diff --git a/rtchallenge/src/world.rs b/rtchallenge/src/world.rs index 93dd7de..7369df2 100644 --- a/rtchallenge/src/world.rs +++ b/rtchallenge/src/world.rs @@ -12,16 +12,6 @@ use crate::{ }; /// World holds all drawable objects and the light(s) that illuminate them. -/// -/// # Examples -/// ``` -/// use rtchallenge::world::World; -/// -/// let w = World::default(); -/// assert!(w.objects.is_empty()); -/// assert_eq!(w.lights.len(), 0); -/// ``` - #[derive(Builder, Clone, Debug, Default)] #[builder(default)] pub struct World { @@ -31,15 +21,6 @@ pub struct World { impl World { /// Creates a world suitable for use across multiple tests in from the book. - /// - /// # Examples - /// ``` - /// use rtchallenge::world::World; - /// - /// let w = World::test_world(); - /// assert_eq!(w.objects.len(), 2); - /// assert!(!w.lights.is_empty()); - /// ``` pub fn test_world() -> World { let light = PointLight::new(Tuple::point(-10., 10., -10.), WHITE); let mut s1 = Shape::sphere(); @@ -57,21 +38,7 @@ impl World { } } - /// Intesects the ray with this world. - /// - /// # Examples - /// ``` - /// use rtchallenge::{rays::Ray, tuples::Tuple, world::World}; - /// - /// let w = World::test_world(); - /// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); - /// let xs = w.intersect(&r); - /// assert_eq!(xs.len(), 4); - /// assert_eq!(xs[0].t, 4.); - /// assert_eq!(xs[1].t, 4.5); - /// assert_eq!(xs[2].t, 5.5); - /// assert_eq!(xs[3].t, 6.); - /// ``` + /// Intersects the ray with this world. pub fn intersect(&self, r: &Ray) -> Intersections { let mut xs: Vec<_> = self .objects @@ -87,104 +54,6 @@ impl World { } /// Compute shaded value for given precomputation. - /// - /// # Examples - /// ``` - /// use rtchallenge::{ - /// intersections::{prepare_computations, Intersection, Intersections}, - /// lights::PointLight, - /// materials::MaterialBuilder, - /// matrices::{translation, Matrix4x4}, - /// rays::Ray, - /// shapes::{plane, sphere, Shape, ShapeBuilder}, - /// tuples::{point, vector, Color, Tuple}, - /// world::World, - /// Float, WHITE, - /// }; - /// - /// # fn main() -> Result<(), Box> { - /// - /// // Shading an intersection. - /// 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 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)); - /// - /// // Shading an intersection from the inside. - /// let mut w = World::test_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 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)); - /// - /// // Shading with an intersection in shadow. - /// let mut w = World::default(); - /// w.lights = vec![PointLight::new(Tuple::point(0., 0., -10.), WHITE)]; - /// let s1 = Shape::sphere(); - /// let mut s2 = Shape::sphere(); - /// 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 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)); - /// - /// // Shading with a reflective material. - /// let mut w = World::test_world(); - /// let shape = ShapeBuilder::plane() - /// .material(MaterialBuilder::default().reflective(0.5).build()?) - /// .transform(Matrix4x4::translation(0., -1., 0.)) - /// .build()?; - /// w.objects.push(shape.clone()); - /// let r = Ray::new( - /// Tuple::point(0., 0., -3.), - /// Tuple::vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.), - /// ); - /// 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(()) - /// # } - /// ``` pub fn shade_hit(&self, comps: &PrecomputedData, remaining: usize) -> Color { let surface = self .lights @@ -213,69 +82,6 @@ impl World { } } /// Compute color for given ray fired at the world. - /// - /// # Examples - /// ``` - /// use rtchallenge::{ - /// intersections::{prepare_computations, Intersection}, - /// lights::PointLight, - /// materials::MaterialBuilder, - /// matrices::translation, - /// rays::Ray, - /// shapes::{Shape, ShapeBuilder}, - /// tuples::{point, vector, Color, Tuple}, - /// world::{World, WorldBuilder}, - /// BLACK, WHITE, - /// }; - /// - /// # fn main() -> Result<(), Box> { - /// - /// // The color when a ray misses. - /// let w = World::test_world(); - /// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 1., 0.)); - /// let c = w.color_at(&r, 1); - /// assert_eq!(c, BLACK); - /// - /// // The color when a ray hits. - /// let w = World::test_world(); - /// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); - /// let c = w.color_at(&r, 1); - /// assert_eq!(c, Color::new(0.38066, 0.47583, 0.2855)); - /// - /// // The color with an intersection behind the ray. - /// let w = { - /// let mut w = World::test_world(); - /// let mut outer = &mut w.objects[0]; - /// let m = outer.material.ambient = 1.; - /// let inner = &mut w.objects[1]; - /// inner.material.ambient = 1.; - /// w - /// }; - /// let inner = &w.objects[1]; - /// let r = Ray::new(Tuple::point(0., 0., 0.75), Tuple::vector(0., 0., -1.)); - /// let c = w.color_at(&r, 1); - /// // inner.material.color is WHITE - /// assert_eq!(c, WHITE); - /// - /// // Ensure mutually reflective surfaces don't infinitely recurse. - /// let lower = ShapeBuilder::plane() - /// .material(MaterialBuilder::default().reflective(1.).build()?) - /// .transform(translation(0., -1., 0.)) - /// .build()?; - /// let upper = ShapeBuilder::plane() - /// .material(MaterialBuilder::default().reflective(1.).build()?) - /// .transform(translation(0., 1., 0.)) - /// .build()?; - /// let w = WorldBuilder::default() - /// .lights(vec![PointLight::new(point(0., 0., 0.), WHITE)]) - /// .objects(vec![lower, upper]) - /// .build()?; - /// let r = Ray::new(point(0., 0., 0.), vector(0., 1., 0.)); - /// // This should complete without stack overflow. - /// w.color_at(&r, 1); - /// # Ok(()) - /// # } - /// ``` pub fn color_at(&self, r: &Ray, remaining: usize) -> Color { let xs = self.intersect(r); match xs.hit() { @@ -288,30 +94,6 @@ impl World { } /// Determine if point in world is in a shadow. - /// - /// # Examples - /// ``` - /// use rtchallenge::{shapes::Shape, tuples::Tuple, world::World}; - /// - /// let w = World::test_world(); - /// let light = &w.lights[0]; - /// - /// // There is no shadow when nothing is collinear with point and light. - /// let p = Tuple::point(0., 10., 0.); - /// assert_eq!(w.is_shadowed(p, light), false); - /// - /// // There shadow when an object is between the point and the light. - /// let p = Tuple::point(10., -10., 10.); - /// assert_eq!(w.is_shadowed(p, light), true); - /// - /// // There is no shadow when an object is behind the light. - /// let p = Tuple::point(-20., 20., -20.); - /// assert_eq!(w.is_shadowed(p, light), false); - /// - /// // There is no shadow when an object is behind the point. - /// let p = Tuple::point(-2., 2., -2.); - /// assert_eq!(w.is_shadowed(p, light), false); - /// ``` pub fn is_shadowed(&self, point: Tuple, light: &PointLight) -> bool { let v = light.position - point; let distance = v.magnitude(); @@ -326,66 +108,6 @@ impl World { } /// Compute reflected color for the given precomputation. - /// - /// # Examples - /// ``` - /// use rtchallenge::{ - /// intersections::{prepare_computations, Intersection, Intersections}, - /// lights::PointLight, - /// materials::MaterialBuilder, - /// matrices::{translation, Matrix4x4}, - /// rays::Ray, - /// shapes::{Shape, ShapeBuilder}, - /// tuples::{point, vector, Color, Tuple}, - /// world::World, - /// Float, BLACK, - /// }; - /// - /// # fn main() -> Result<(), Box> { - /// - /// // The reflected color for a nonreflective material. - /// 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 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); - /// - /// // The reflected color for a reflective material. - /// let mut w = World::test_world(); - /// let shape = ShapeBuilder::plane() - /// .material(MaterialBuilder::default().reflective(0.5).build()?) - /// .transform(Matrix4x4::translation(0., -1., 0.)) - /// .build()?; - /// w.objects.push(shape.clone()); - /// let r = Ray::new( - /// Tuple::point(0., 0., -3.), - /// Tuple::vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.), - /// ); - /// 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()); - /// - /// // The reflected color at the maximum recursion depth. - /// let mut w = World::test_world(); - /// let shape = ShapeBuilder::plane() - /// .material(MaterialBuilder::default().reflective(0.5).build()?) - /// .transform(translation(0., -1., 0.)) - /// .build()?; - /// w.objects.push(shape.clone()); - /// let r = Ray::new( - /// point(0., 0., -3.), - /// vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.), - /// ); - /// 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(()) - /// # } - /// ``` pub fn reflected_color(&self, comps: &PrecomputedData, remaining: usize) -> Color { if remaining == 0 { return BLACK; @@ -400,86 +122,6 @@ impl World { } /// 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; @@ -513,3 +155,406 @@ impl World { self.color_at(&refract_ray, remaining - 1) * comps.object.material.transparency } } + +#[cfg(test)] +mod tests { + use crate::{ + float::consts::SQRT_2, + intersections::{prepare_computations, Intersection, Intersections}, + lights::PointLight, + materials::MaterialBuilder, + matrices::translation, + patterns::test_pattern, + rays::Ray, + shapes::{plane, sphere, Shape, ShapeBuilder}, + tuples::{point, vector}, + world::{World, WorldBuilder}, + Float, BLACK, WHITE, + }; + + mod intersect { + use super::*; + + #[test] + fn intersection_with_test_world() { + let w = World::test_world(); + let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.)); + let xs = w.intersect(&r); + assert_eq!(xs.len(), 4); + assert_eq!(xs[0].t, 4.); + assert_eq!(xs[1].t, 4.5); + assert_eq!(xs[2].t, 5.5); + assert_eq!(xs[3].t, 6.); + } + } + mod world { + use super::*; + + #[test] + fn default_world() { + let w = World::default(); + assert!(w.objects.is_empty()); + assert_eq!(w.lights.len(), 0); + } + #[test] + fn test_world() { + let w = World::test_world(); + assert_eq!(w.objects.len(), 2); + assert!(!w.lights.is_empty()); + } + } + mod shade_hit { + use super::*; + + #[test] + fn shading_an_intersection() { + // Shading an intersection. + let w = World::test_world(); + let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.)); + let s = &w.objects[0]; + 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, [0.38066, 0.47583, 0.2855].into()); + } + #[test] + fn shading_an_intersection_from_inside() { + // Shading an intersection from the inside. + let mut w = World::test_world(); + w.lights = vec![PointLight::new(point(0., 0.25, 0.), WHITE)]; + let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.)); + let s = &w.objects[1]; + 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, [0.90498, 0.90498, 0.90498].into()); + } + #[test] + fn shading_an_intersection_in_shadow() { + // Shading with an intersection in shadow. + let mut w = World::default(); + w.lights = vec![PointLight::new(point(0., 0., -10.), WHITE)]; + let s1 = Shape::sphere(); + let mut s2 = Shape::sphere(); + s2.set_transform(translation(0., 0., 10.)); + w.objects = vec![s1, s2.clone()]; + let r = Ray::new(point(0., 0., 5.), vector(0., 0., 1.)); + 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, [0.1, 0.1, 0.1].into()); + } + #[test] + fn shading_with_a_reflective_material() -> Result<(), Box> { + // Shading with a reflective material. + let mut w = World::test_world(); + let shape = ShapeBuilder::plane() + .material(MaterialBuilder::default().reflective(0.5).build()?) + .transform(translation(0., -1., 0.)) + .build()?; + w.objects.push(shape.clone()); + let r = Ray::new( + point(0., 0., -3.), + vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.), + ); + 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()); + Ok(()) + } + #[test] + fn shading_with_a_transparent_material() -> Result<(), Box> { + // 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(()) + } + #[test] + fn 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(()) + } + } + mod color_at { + use super::*; + + #[test] + fn color_when_ray_misses() { + // The color when a ray misses. + let w = World::test_world(); + let r = Ray::new(point(0., 0., -5.), vector(0., 1., 0.)); + let c = w.color_at(&r, 1); + assert_eq!(c, BLACK); + } + #[test] + fn color_when_ray_hits() { + // The color when a ray hits. + let w = World::test_world(); + let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.)); + let c = w.color_at(&r, 1); + assert_eq!(c, [0.38066, 0.47583, 0.2855].into()); + } + #[test] + fn color_with_an_intersection_behind_ray() { + // The color with an intersection behind the ray. + let w = { + let mut w = World::test_world(); + let mut outer = &mut w.objects[0]; + outer.material.ambient = 1.; + let inner = &mut w.objects[1]; + inner.material.ambient = 1.; + w + }; + let r = Ray::new(point(0., 0., 0.75), vector(0., 0., -1.)); + let c = w.color_at(&r, 1); + // inner.material.color is WHITE + assert_eq!(c, WHITE); + } + #[test] + fn ensure_mutually_reflective_surfaces_terminate() -> Result<(), Box> + { + // Ensure mutually reflective surfaces don't infinitely recurse. + let lower = ShapeBuilder::plane() + .material(MaterialBuilder::default().reflective(1.).build()?) + .transform(translation(0., -1., 0.)) + .build()?; + let upper = ShapeBuilder::plane() + .material(MaterialBuilder::default().reflective(1.).build()?) + .transform(translation(0., 1., 0.)) + .build()?; + let w = WorldBuilder::default() + .lights(vec![PointLight::new(point(0., 0., 0.), WHITE)]) + .objects(vec![lower, upper]) + .build()?; + let r = Ray::new(point(0., 0., 0.), vector(0., 1., 0.)); + // This should complete without stack overflow. + w.color_at(&r, 1); + Ok(()) + } + } + mod is_shadowed { + use super::*; + + #[test] + fn no_shadow_when_nothing_collinear_with_point_and_light() { + // There is no shadow when nothing is collinear with point and light. + let w = World::test_world(); + let light = &w.lights[0]; + + let p = point(0., 10., 0.); + assert_eq!(w.is_shadowed(p, light), false); + } + #[test] + fn shadow_when_object_between_point_and_light() { + // The shadow when an object is between the point and the light. + let w = World::test_world(); + let light = &w.lights[0]; + + let p = point(10., -10., 10.); + assert_eq!(w.is_shadowed(p, light), true); + } + + #[test] + fn no_shadow_when_object_behind_light() { + // There is no shadow when an object is behind the light. + let w = World::test_world(); + let light = &w.lights[0]; + + let p = point(-20., 20., -20.); + assert_eq!(w.is_shadowed(p, light), false); + } + #[test] + fn no_shadow_when_object_behind_point() { + // There is no shadow when an object is behind the point. + let w = World::test_world(); + let light = &w.lights[0]; + + let p = point(-2., 2., -2.); + assert_eq!(w.is_shadowed(p, light), false); + } + } + + mod reflected_color { + use super::*; + + #[test] + fn reflected_color_for_nonreflective_material() { + // The reflected color for a nonreflective material. + let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.)); + let mut w = World::test_world(); + w.objects[1].material.ambient = 1.; + 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); + } + #[test] + fn reflected_color_for_reflective_material() -> Result<(), Box> { + // The reflected color for a reflective material. + let mut w = World::test_world(); + let shape = ShapeBuilder::plane() + .material(MaterialBuilder::default().reflective(0.5).build()?) + .transform(translation(0., -1., 0.)) + .build()?; + w.objects.push(shape.clone()); + let r = Ray::new( + point(0., 0., -3.), + vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.), + ); + 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()); + Ok(()) + } + #[test] + fn reflected_color_at_maximum_recursion_depth() -> Result<(), Box> { + // The reflected color at the maximum recursion depth. + let mut w = World::test_world(); + let shape = ShapeBuilder::plane() + .material(MaterialBuilder::default().reflective(0.5).build()?) + .transform(translation(0., -1., 0.)) + .build()?; + w.objects.push(shape.clone()); + let r = Ray::new( + point(0., 0., -3.), + vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.), + ); + let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape)); + let comps = prepare_computations(&xs[0], &r, &xs); + let _ = w.reflected_color(&comps, 0); + // Just needs to get here without infinite recursion. + + Ok(()) + } + } + mod refracted_color { + use super::*; + + #[test] + fn refracted_color_with_opaque_surface() { + // The refracted color with an opaque surface. + let w = World::test_world(); + let shape = &w.objects[0]; + let r = Ray::new(point(0., 0., -5.), 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); + } + #[test] + fn refracted_color_at_maximum_recursion_depth() { + // 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 shape = &w.objects[0]; + let r = Ray::new(point(0., 0., -5.), 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); + } + #[test] + fn refracted_color_under_total_internal_reflection() { + // 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 shape = &w.objects[0]; + 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); + } + #[test] + fn refracted_color_with_a_refracted_ray() -> Result<(), Box> { + // 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(()) + } + } +}