Implement transparency, reflections and refraction.
This commit is contained in:
parent
1d61f59935
commit
42e8ebe3bd
@ -64,6 +64,13 @@ impl<'i> Intersection<'i> {
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Intersections<'i>(Vec<Intersection<'i>>);
|
||||
|
||||
/// Create an [Intersections] from a single [Intersection] to aid in tests.
|
||||
impl<'i> From<Intersection<'i>> for Intersections<'i> {
|
||||
fn from(i: Intersection<'i>) -> Intersections<'i> {
|
||||
Intersections::new(vec![i])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i> Intersections<'i> {
|
||||
pub fn new(xs: Vec<Intersection<'i>>) -> Intersections {
|
||||
Intersections(xs)
|
||||
@ -145,11 +152,14 @@ pub struct PrecomputedData<'i> {
|
||||
pub t: Float,
|
||||
pub object: &'i Shape,
|
||||
pub point: Tuple,
|
||||
pub under_point: Tuple,
|
||||
pub over_point: Tuple,
|
||||
pub eyev: Tuple,
|
||||
pub normalv: Tuple,
|
||||
pub reflectv: Tuple,
|
||||
pub inside: bool,
|
||||
pub n1: Float,
|
||||
pub n2: Float,
|
||||
}
|
||||
|
||||
/// Precomputes data common to all intersections.
|
||||
@ -159,19 +169,22 @@ pub struct PrecomputedData<'i> {
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{prepare_computations, Intersection, Intersections},
|
||||
/// rays::Ray,
|
||||
/// matrices::Matrix4x4,
|
||||
/// shapes::{intersect, Shape},
|
||||
/// materials::MaterialBuilder,
|
||||
/// matrices::{Matrix4x4,scaling,translation},
|
||||
/// shapes::{intersect, Shape,glass_sphere},
|
||||
/// tuples::{point,vector,Tuple},
|
||||
/// EPSILON,Float
|
||||
/// };
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
///
|
||||
/// // Precomputing the state of an intersection.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let shape = Shape::sphere();
|
||||
/// let i = Intersection::new(4., &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// assert_eq!(comps.t, i.t);
|
||||
/// assert_eq!(comps.object, i.object);
|
||||
/// let xs = Intersections::from(Intersection::new(4., &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert_eq!(comps.t, xs[0].t);
|
||||
/// assert_eq!(comps.object, xs[0].object);
|
||||
/// assert_eq!(comps.point, Tuple::point(0., 0., -1.));
|
||||
/// assert_eq!(comps.eyev, Tuple::vector(0., 0., -1.));
|
||||
/// assert_eq!(comps.normalv, Tuple::vector(0., 0., -1.));
|
||||
@ -179,15 +192,15 @@ pub struct PrecomputedData<'i> {
|
||||
/// // The hit, when an intersection occurs on the outside.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let shape = Shape::sphere();
|
||||
/// let i = Intersection::new(4., &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new(4., &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert_eq!(comps.inside, false);
|
||||
///
|
||||
/// // The hit, when an intersection occurs on the inside.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
||||
/// let shape = Shape::sphere();
|
||||
/// let i = Intersection::new(1., &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new(1., &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert_eq!(comps.point, Tuple::point(0., 0., 1.));
|
||||
/// assert_eq!(comps.eyev, Tuple::vector(0., 0., -1.));
|
||||
/// assert_eq!(comps.inside, true);
|
||||
@ -198,21 +211,61 @@ pub struct PrecomputedData<'i> {
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let mut shape = Shape::sphere();
|
||||
/// shape .set_transform(Matrix4x4::translation(0.,0.,1.));
|
||||
/// let i = Intersection::new(5., &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new(5., &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert!(comps.over_point.z< -EPSILON/2.);
|
||||
/// assert!(comps.point.z>comps.over_point.z);
|
||||
///
|
||||
/// // Precomputing the reflection vector.
|
||||
/// let shape = Shape::plane();
|
||||
/// let r= Ray::new(point(0.,1.,-1.), vector(0.,-(2 as Float).sqrt()/2., (2 as Float).sqrt()/2.));
|
||||
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert_eq!(comps.reflectv, vector(0.,(2 as Float).sqrt()/2., (2 as Float).sqrt()/2.));
|
||||
///
|
||||
/// // Finding n1 and n2 at various intersections.
|
||||
/// let a = glass_sphere().transform(scaling(2.,2.,2.)).material(MaterialBuilder::default().transparency(1.).refractive_index(1.5).build()?).build()?;
|
||||
/// let b = glass_sphere().transform(translation(0.,0.,-0.25)).material(MaterialBuilder::default().transparency(1.).refractive_index(2.).build()?).build()?;
|
||||
/// let c = glass_sphere().transform(translation(0.,0.,0.25)).material(MaterialBuilder::default().transparency(1.).refractive_index(2.5).build()?).build()?;
|
||||
/// let r = Ray::new(point(0.,0.,-4.), vector(0.,0.,1.));
|
||||
/// let xs = Intersections::new(vec![
|
||||
/// Intersection::new(2., &a),
|
||||
/// Intersection::new(2.75, &b),
|
||||
/// Intersection::new(3.25, &c),
|
||||
/// Intersection::new(4.75, &b),
|
||||
/// Intersection::new(5.25, &c),
|
||||
/// Intersection::new(6., &a),
|
||||
/// ]);
|
||||
/// for (index, n1, n2) in &[
|
||||
/// (0,1.0,1.5),
|
||||
/// (1,1.5,2.0),
|
||||
/// (2,2.0,2.5),
|
||||
/// (3,2.5,2.5),
|
||||
/// (4,2.5,1.5),
|
||||
/// (5,1.5,1.0)]{
|
||||
/// let comps = prepare_computations(&xs[*index], &r, &xs);
|
||||
/// assert_eq!(comps.n1, *n1);
|
||||
/// assert_eq!(comps.n2, *n2);
|
||||
/// }
|
||||
///
|
||||
/// // The under point is offset below the surface.
|
||||
/// let r = Ray::new(point(0.,0.,-5.), vector(0.,0.,1.));
|
||||
/// let shape = glass_sphere().transform(translation(0.,0.,1.)).build()?;
|
||||
/// let xs = Intersections::from(Intersection::new(5.,&shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// assert!(comps.under_point.z > EPSILON/2.);
|
||||
/// assert!(comps.point.z < comps.under_point.z);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData<'i> {
|
||||
let point = r.position(i.t);
|
||||
let normalv = i.object.normal_at(point);
|
||||
pub fn prepare_computations<'i>(
|
||||
hit: &'i Intersection,
|
||||
r: &Ray,
|
||||
xs: &Intersections,
|
||||
) -> PrecomputedData<'i> {
|
||||
let point = r.position(hit.t);
|
||||
let normalv = hit.object.normal_at(point);
|
||||
let eyev = -r.direction;
|
||||
let (inside, normalv) = if dot(normalv, eyev) < 0. {
|
||||
(true, -normalv)
|
||||
@ -221,15 +274,154 @@ pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData
|
||||
};
|
||||
let reflectv = reflect(r.direction, normalv);
|
||||
|
||||
let mut n1 = -1.;
|
||||
let mut n2 = -1.;
|
||||
let mut containers: Vec<&Shape> = Vec::new();
|
||||
for i in xs.0.iter() {
|
||||
if hit == i {
|
||||
if containers.is_empty() {
|
||||
n1 = 1.;
|
||||
} else {
|
||||
n1 = containers.last().unwrap().material.refractive_index;
|
||||
}
|
||||
}
|
||||
|
||||
match containers.iter().position(|o| o == &i.object) {
|
||||
Some(idx) => {
|
||||
containers.remove(idx);
|
||||
}
|
||||
None => containers.push(i.object),
|
||||
}
|
||||
|
||||
if hit == i {
|
||||
if containers.is_empty() {
|
||||
n2 = 1.;
|
||||
} else {
|
||||
n2 = containers.last().unwrap().material.refractive_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let over_point = point + normalv * EPSILON;
|
||||
let under_point = point - normalv * EPSILON;
|
||||
PrecomputedData {
|
||||
t: i.t,
|
||||
object: i.object,
|
||||
t: hit.t,
|
||||
object: hit.object,
|
||||
point,
|
||||
over_point,
|
||||
under_point,
|
||||
normalv,
|
||||
reflectv,
|
||||
inside,
|
||||
eyev,
|
||||
n1,
|
||||
n2,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute Schlick approximation to Fresnel's equations.
|
||||
pub fn schlick(comps: &PrecomputedData) -> Float {
|
||||
// Find the cosine of the angle between the eye and normal vectors.
|
||||
let mut cos = dot(comps.eyev, comps.normalv);
|
||||
// Total internal reflection can only occur if n1 > n2.
|
||||
if comps.n1 > comps.n2 {
|
||||
let n = comps.n1 / comps.n2;
|
||||
let sin2_t = n * n * (1. - cos * cos);
|
||||
if sin2_t > 1. {
|
||||
return 1.;
|
||||
}
|
||||
// Compute cosine of theta_t using trig identity.
|
||||
let cos_t = (1. - sin2_t).sqrt();
|
||||
// When n1 > n2 use cos(theta_t) instead.
|
||||
cos = cos_t;
|
||||
}
|
||||
|
||||
let r0 = (comps.n1 - comps.n2) / (comps.n1 + comps.n2);
|
||||
let r0 = r0 * r0;
|
||||
r0 + (1. - r0) * (1. - cos) * (1. - cos) * (1. - cos) * (1. - cos) * (1. - cos)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
float::consts::SQRT_2,
|
||||
intersections::{prepare_computations, Intersection, Intersections},
|
||||
materials::MaterialBuilder,
|
||||
matrices::translation,
|
||||
rays::Ray,
|
||||
shapes::{glass_sphere, plane, sphere},
|
||||
tuples::{point, vector},
|
||||
world::World,
|
||||
Float, EPSILON,
|
||||
};
|
||||
|
||||
use super::schlick;
|
||||
#[test]
|
||||
fn schlick_under_total_reflection() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
let r = Ray::new(point(0., 0., (2 as Float).sqrt() / 2.), vector(0., 1., 0.));
|
||||
let xs = Intersections::new(vec![
|
||||
Intersection::new(-(2 as Float).sqrt() / 2., &shape),
|
||||
Intersection::new((2 as Float).sqrt() / 2., &shape),
|
||||
]);
|
||||
let comps = prepare_computations(&xs[1], &r, &xs);
|
||||
let reflectance = schlick(&comps);
|
||||
assert_eq!(reflectance, 1.);
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn schlick_perpendicular_viewing_angle() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
let r = Ray::new(point(0., 0., 0.), vector(0., 1., 0.));
|
||||
let xs = Intersections::new(vec![
|
||||
Intersection::new(-1., &shape),
|
||||
Intersection::new(1., &shape),
|
||||
]);
|
||||
let comps = prepare_computations(&xs[1], &r, &xs);
|
||||
let reflectance = schlick(&comps);
|
||||
assert!((reflectance - 0.04).abs() < EPSILON);
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn schlick_small_angle_n2_greater_n1() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
let r = Ray::new(point(0., 0.99, -2.), vector(0., 0., 1.));
|
||||
let xs = Intersections::new(vec![Intersection::new(1.8589, &shape)]);
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
let reflectance = schlick(&comps);
|
||||
assert!((reflectance - 0.48873).abs() < EPSILON);
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn shade_hit_reflective_transparent_material() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut w = World::test_world();
|
||||
let floor = plane()
|
||||
.transform(translation(0., -1., 0.))
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.reflective(0.5)
|
||||
.transparency(0.5)
|
||||
.refractive_index(1.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
w.objects.push(floor.clone());
|
||||
let ball = sphere()
|
||||
.transform(translation(0., -3.5, -0.5))
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color([1., 0., 0.])
|
||||
.ambient(0.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
w.objects.push(ball);
|
||||
let r = Ray::new(point(0., 0., -3.), vector(0., -SQRT_2 / 2., SQRT_2 / 2.));
|
||||
let xs = Intersections::new(vec![Intersection::new(SQRT_2, &floor)]);
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
let color = w.shade_hit(&comps, 5);
|
||||
assert_eq!(color, [0.93391, 0.69643, 0.69243].into());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,10 @@ use std::sync::{Arc, Mutex};
|
||||
use derive_builder::Builder;
|
||||
|
||||
use crate::{
|
||||
intersections::Intersections, materials::Material, matrices::Matrix4x4, rays::Ray,
|
||||
intersections::Intersections,
|
||||
materials::{Material, MaterialBuilder},
|
||||
matrices::Matrix4x4,
|
||||
rays::Ray,
|
||||
tuples::Tuple,
|
||||
};
|
||||
|
||||
@ -80,6 +83,7 @@ pub fn plane() -> ShapeBuilder {
|
||||
pub fn sphere() -> ShapeBuilder {
|
||||
ShapeBuilder::sphere()
|
||||
}
|
||||
|
||||
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
||||
///
|
||||
/// # Examples
|
||||
@ -95,6 +99,31 @@ pub fn test_shape() -> ShapeBuilder {
|
||||
ShapeBuilder::test_shape()
|
||||
}
|
||||
|
||||
/// Helper for producing a sphere with a glassy material.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{matrices::identity, shapes::glass_sphere};
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
///
|
||||
/// let s = glass_sphere().build()?;
|
||||
/// assert_eq!(s.transform(), identity());
|
||||
/// assert_eq!(s.material.transparency, 1.);
|
||||
/// assert_eq!(s.material.refractive_index, 1.5);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn glass_sphere() -> ShapeBuilder {
|
||||
ShapeBuilder::sphere().material(
|
||||
MaterialBuilder::default()
|
||||
.transparency(1.)
|
||||
.refractive_index(1.5)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
impl ShapeBuilder {
|
||||
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
||||
///
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
use derive_builder::Builder;
|
||||
|
||||
use crate::{
|
||||
intersections::{prepare_computations, Intersections, PrecomputedData},
|
||||
intersections::{prepare_computations, schlick, Intersections, PrecomputedData},
|
||||
lights::PointLight,
|
||||
materials::{lighting, Material},
|
||||
matrices::Matrix4x4,
|
||||
rays::Ray,
|
||||
shapes::{intersect, Shape},
|
||||
tuples::{Color, Tuple},
|
||||
tuples::{dot, Color, Tuple},
|
||||
BLACK, WHITE,
|
||||
};
|
||||
|
||||
@ -91,13 +91,13 @@ impl World {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{prepare_computations, Intersection},
|
||||
/// intersections::{prepare_computations, Intersection, Intersections},
|
||||
/// lights::PointLight,
|
||||
/// materials::MaterialBuilder,
|
||||
/// matrices::Matrix4x4,
|
||||
/// matrices::{translation, Matrix4x4},
|
||||
/// rays::Ray,
|
||||
/// shapes::{Shape, ShapeBuilder},
|
||||
/// tuples::{Color, Tuple},
|
||||
/// shapes::{plane, sphere, Shape, ShapeBuilder},
|
||||
/// tuples::{point, vector, Color, Tuple},
|
||||
/// world::World,
|
||||
/// Float, WHITE,
|
||||
/// };
|
||||
@ -108,8 +108,8 @@ impl World {
|
||||
/// let w = World::test_world();
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let s = &w.objects[0];
|
||||
/// let i = Intersection::new(4., &s);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new(4., &s));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.shade_hit(&comps, 1);
|
||||
/// assert_eq!(c, Color::new(0.38066, 0.47583, 0.2855));
|
||||
///
|
||||
@ -118,8 +118,8 @@ impl World {
|
||||
/// w.lights = vec![PointLight::new(Tuple::point(0., 0.25, 0.), WHITE)];
|
||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
||||
/// let s = &w.objects[1];
|
||||
/// let i = Intersection::new(0.5, &s);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new(0.5, &s));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.shade_hit(&comps, 1);
|
||||
/// assert_eq!(c, Color::new(0.90498, 0.90498, 0.90498));
|
||||
///
|
||||
@ -131,8 +131,8 @@ impl World {
|
||||
/// s2.set_transform(Matrix4x4::translation(0., 0., 10.));
|
||||
/// w.objects = vec![s1, s2.clone()];
|
||||
/// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
|
||||
/// let i = Intersection::new(4., &s2);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new(4., &s2));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.shade_hit(&comps, 1);
|
||||
/// assert_eq!(c, Color::new(0.1, 0.1, 0.1));
|
||||
///
|
||||
@ -147,11 +147,41 @@ impl World {
|
||||
/// Tuple::point(0., 0., -3.),
|
||||
/// Tuple::vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||
/// );
|
||||
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.shade_hit(&comps, 1);
|
||||
/// assert_eq!(c, [0.87676, 0.92434, 0.82917].into());
|
||||
///
|
||||
/// // shade_hit() with a transparent material.
|
||||
/// let mut w = World::test_world();
|
||||
/// let floor = plane()
|
||||
/// .transform(translation(0., -1., 0.))
|
||||
/// .material(
|
||||
/// MaterialBuilder::default()
|
||||
/// .transparency(0.5)
|
||||
/// .refractive_index(1.5)
|
||||
/// .build()?,
|
||||
/// )
|
||||
/// .build()?;
|
||||
/// w.objects.push(floor.clone());
|
||||
/// let ball = sphere()
|
||||
/// .material(
|
||||
/// MaterialBuilder::default()
|
||||
/// .color([1., 0., 0.])
|
||||
/// .ambient(0.5)
|
||||
/// .build()?,
|
||||
/// )
|
||||
/// .transform(translation(0., -3.5, -0.5))
|
||||
/// .build()?;
|
||||
/// w.objects.push(ball);
|
||||
/// let r = Ray::new(
|
||||
/// point(0., 0., -3.),
|
||||
/// vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||
/// );
|
||||
/// let xs = Intersections::new(vec![Intersection::new((2 as Float).sqrt(), &floor)]);
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.shade_hit(&comps, 5);
|
||||
/// assert_eq!(c, [0.93642, 0.68642, 0.68643].into());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@ -173,7 +203,14 @@ impl World {
|
||||
acc + surface
|
||||
});
|
||||
let reflected = self.reflected_color(comps, remaining);
|
||||
surface + reflected
|
||||
let refracted = self.refracted_color(comps, remaining);
|
||||
let material = &comps.object.material;
|
||||
if material.reflective > 0. && material.transparency > 0. {
|
||||
let reflectance = schlick(comps);
|
||||
surface + reflected * reflectance + refracted * (1. - reflectance)
|
||||
} else {
|
||||
surface + reflected + refracted
|
||||
}
|
||||
}
|
||||
/// Compute color for given ray fired at the world.
|
||||
///
|
||||
@ -240,9 +277,10 @@ impl World {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn color_at(&self, r: &Ray, remaining: usize) -> Color {
|
||||
match self.intersect(r).hit() {
|
||||
let xs = self.intersect(r);
|
||||
match xs.hit() {
|
||||
Some(hit) => {
|
||||
let comps = prepare_computations(&hit, r);
|
||||
let comps = prepare_computations(&hit, r, &xs);
|
||||
self.shade_hit(&comps, remaining)
|
||||
}
|
||||
None => BLACK,
|
||||
@ -292,7 +330,7 @@ impl World {
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{prepare_computations, Intersection},
|
||||
/// intersections::{prepare_computations, Intersection, Intersections},
|
||||
/// lights::PointLight,
|
||||
/// materials::MaterialBuilder,
|
||||
/// matrices::{translation, Matrix4x4},
|
||||
@ -309,8 +347,8 @@ impl World {
|
||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
||||
/// let mut w = World::test_world();
|
||||
/// w.objects[1].material.ambient = 1.;
|
||||
/// let i = Intersection::new(1., &w.objects[1]);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new(1., &w.objects[1]));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.reflected_color(&comps, 1);
|
||||
/// assert_eq!(c, BLACK);
|
||||
///
|
||||
@ -325,8 +363,8 @@ impl World {
|
||||
/// Tuple::point(0., 0., -3.),
|
||||
/// Tuple::vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||
/// );
|
||||
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.reflected_color(&comps, 1);
|
||||
/// assert_eq!(c, [0.19033, 0.23791, 0.14274].into());
|
||||
///
|
||||
@ -341,8 +379,8 @@ impl World {
|
||||
/// point(0., 0., -3.),
|
||||
/// vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||
/// );
|
||||
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.reflected_color(&comps, 0);
|
||||
///
|
||||
/// # Ok(())
|
||||
@ -360,4 +398,118 @@ impl World {
|
||||
let color = self.color_at(&reflect_ray, remaining - 1);
|
||||
color * comps.object.material.reflective
|
||||
}
|
||||
|
||||
/// Compute refracted color for the given precomputation.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{prepare_computations, Intersection, Intersections},
|
||||
/// lights::PointLight,
|
||||
/// materials::MaterialBuilder,
|
||||
/// matrices::{translation, Matrix4x4},
|
||||
/// patterns::test_pattern,
|
||||
/// rays::Ray,
|
||||
/// shapes::{plane, sphere, Shape, ShapeBuilder},
|
||||
/// tuples::{point, vector, Color, Tuple},
|
||||
/// world::World,
|
||||
/// Float, BLACK,
|
||||
/// };
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
///
|
||||
/// // The refracted color with an opaque surface.
|
||||
/// let mut w = World::test_world();
|
||||
/// let shape = &w.objects[0];
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let xs = Intersections::new(vec![
|
||||
/// Intersection::new(4., &shape),
|
||||
/// Intersection::new(6., &shape),
|
||||
/// ]);
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.refracted_color(&comps, 5);
|
||||
/// assert_eq!(c, BLACK);
|
||||
///
|
||||
/// // The refracted color at the maximum recursive depth.
|
||||
/// let mut w = World::test_world();
|
||||
/// w.objects[0].material.transparency = 1.0;
|
||||
/// w.objects[0].material.refractive_index = 1.5;
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let xs = Intersections::new(vec![
|
||||
/// Intersection::new(4., &shape),
|
||||
/// Intersection::new(6., &shape),
|
||||
/// ]);
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// let c = w.refracted_color(&comps, 0);
|
||||
/// assert_eq!(c, BLACK);
|
||||
///
|
||||
/// // The refracted color under total internal reflection.
|
||||
/// let mut w = World::test_world();
|
||||
/// w.objects[0].material.transparency = 1.0;
|
||||
/// w.objects[0].material.refractive_index = 1.5;
|
||||
/// let r = Ray::new(point(0., 0., (2 as Float).sqrt() / 2.), vector(0., 1., 0.));
|
||||
/// let xs = Intersections::new(vec![
|
||||
/// Intersection::new(-(2 as Float).sqrt() / 2., &shape),
|
||||
/// Intersection::new((2 as Float).sqrt() / 2., &shape),
|
||||
/// ]);
|
||||
/// let comps = prepare_computations(&xs[1], &r, &xs);
|
||||
/// let c = w.refracted_color(&comps, 5);
|
||||
/// assert_eq!(c, BLACK);
|
||||
///
|
||||
/// // The refracted color with a refracted ray.
|
||||
/// let mut w = World::test_world();
|
||||
/// w.objects[0].material.ambient = 1.;
|
||||
/// w.objects[0].material.color = test_pattern().build()?;
|
||||
///
|
||||
/// w.objects[1].material.transparency = 1.;
|
||||
/// w.objects[1].material.refractive_index = 1.5;
|
||||
///
|
||||
/// let r = Ray::new(point(0., 0., 0.1), vector(0., 1., 0.));
|
||||
/// let a = &w.objects[0];
|
||||
/// let b = &w.objects[1];
|
||||
/// let xs = Intersections::new(vec![
|
||||
/// Intersection::new(-0.9899, &a),
|
||||
/// Intersection::new(-0.4899, &b),
|
||||
/// Intersection::new(0.4899, &b),
|
||||
/// Intersection::new(0.9899, &a),
|
||||
/// ]);
|
||||
/// let comps = prepare_computations(&xs[2], &r, &xs);
|
||||
/// let c = w.refracted_color(&comps, 5);
|
||||
/// assert_eq!(c, [0., 0.99887, 0.04721].into());
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn refracted_color(&self, comps: &PrecomputedData, remaining: usize) -> Color {
|
||||
if remaining == 0 {
|
||||
return BLACK;
|
||||
}
|
||||
if comps.object.material.transparency == 0. {
|
||||
return BLACK;
|
||||
}
|
||||
// Find the ratio of the first index of refraction to the secon
|
||||
// (This is inverted from the definition of Snell's Law.)
|
||||
let n_ratio = comps.n1 / comps.n2;
|
||||
|
||||
// cos(theta_i) is the same as the dot product of the two vectors
|
||||
// TODO(wathiede): is cosine faster than doc productings?
|
||||
let cos_i = dot(comps.eyev, comps.normalv);
|
||||
|
||||
// Find the sin(theta_t)^2 via trigonometric identity.
|
||||
let sin2_t = n_ratio * n_ratio * (1. - cos_i * cos_i);
|
||||
|
||||
if sin2_t > 1. {
|
||||
// Total internal reflection.
|
||||
return BLACK;
|
||||
}
|
||||
// Find cos(theta_t) via trigonometric identity.
|
||||
let cos_t = (1. - sin2_t).sqrt();
|
||||
// Compute the direction of the refracted ray.
|
||||
let direction = comps.normalv * (n_ratio * cos_i - cos_t) - comps.eyev * n_ratio;
|
||||
// Create the refracted ray.
|
||||
let refract_ray = Ray::new(comps.under_point, direction);
|
||||
// Find he color of the refracted ray, making sure to multiply by the transparency value to
|
||||
// account for any opacity.
|
||||
self.color_at(&refract_ray, remaining - 1) * comps.object.material.transparency
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user