diff --git a/rtchallenge/src/intersections.rs b/rtchallenge/src/intersections.rs index 9c47e35..6ea80e6 100644 --- a/rtchallenge/src/intersections.rs +++ b/rtchallenge/src/intersections.rs @@ -64,6 +64,13 @@ impl<'i> Intersection<'i> { #[derive(Debug, Default, PartialEq)] pub struct Intersections<'i>(Vec>); +/// Create an [Intersections] from a single [Intersection] to aid in tests. +impl<'i> From> for Intersections<'i> { + fn from(i: Intersection<'i>) -> Intersections<'i> { + Intersections::new(vec![i]) + } +} + impl<'i> Intersections<'i> { pub fn new(xs: Vec>) -> 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> { +/// /// // 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> { + 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> { + 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> { + 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> { + 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(()) } } diff --git a/rtchallenge/src/shapes.rs b/rtchallenge/src/shapes.rs index a831907..6c359dd 100644 --- a/rtchallenge/src/shapes.rs +++ b/rtchallenge/src/shapes.rs @@ -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> { +/// +/// 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. /// diff --git a/rtchallenge/src/world.rs b/rtchallenge/src/world.rs index 6a6868d..93dd7de 100644 --- a/rtchallenge/src/world.rs +++ b/rtchallenge/src/world.rs @@ -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> { + /// + /// // 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 + } }