world: move tests from doctest to unit.
This commit is contained in:
parent
5debb16d10
commit
cd2a4770ca
@ -12,16 +12,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// World holds all drawable objects and the light(s) that illuminate them.
|
/// 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)]
|
#[derive(Builder, Clone, Debug, Default)]
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
@ -31,15 +21,6 @@ pub struct World {
|
|||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
/// Creates a world suitable for use across multiple tests in from the book.
|
/// 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 {
|
pub fn test_world() -> World {
|
||||||
let light = PointLight::new(Tuple::point(-10., 10., -10.), WHITE);
|
let light = PointLight::new(Tuple::point(-10., 10., -10.), WHITE);
|
||||||
let mut s1 = Shape::sphere();
|
let mut s1 = Shape::sphere();
|
||||||
@ -57,21 +38,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intesects the ray with this world.
|
/// Intersects 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.);
|
|
||||||
/// ```
|
|
||||||
pub fn intersect(&self, r: &Ray) -> Intersections {
|
pub fn intersect(&self, r: &Ray) -> Intersections {
|
||||||
let mut xs: Vec<_> = self
|
let mut xs: Vec<_> = self
|
||||||
.objects
|
.objects
|
||||||
@ -87,104 +54,6 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute shaded value for given precomputation.
|
/// 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<dyn std::error::Error>> {
|
|
||||||
///
|
|
||||||
/// // 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 {
|
pub fn shade_hit(&self, comps: &PrecomputedData, remaining: usize) -> Color {
|
||||||
let surface = self
|
let surface = self
|
||||||
.lights
|
.lights
|
||||||
@ -213,69 +82,6 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Compute color for given ray fired at the 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<dyn std::error::Error>> {
|
|
||||||
///
|
|
||||||
/// // 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 {
|
pub fn color_at(&self, r: &Ray, remaining: usize) -> Color {
|
||||||
let xs = self.intersect(r);
|
let xs = self.intersect(r);
|
||||||
match xs.hit() {
|
match xs.hit() {
|
||||||
@ -288,30 +94,6 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if point in world is in a shadow.
|
/// 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 {
|
pub fn is_shadowed(&self, point: Tuple, light: &PointLight) -> bool {
|
||||||
let v = light.position - point;
|
let v = light.position - point;
|
||||||
let distance = v.magnitude();
|
let distance = v.magnitude();
|
||||||
@ -326,66 +108,6 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute reflected color for the given precomputation.
|
/// 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<dyn std::error::Error>> {
|
|
||||||
///
|
|
||||||
/// // 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 {
|
pub fn reflected_color(&self, comps: &PrecomputedData, remaining: usize) -> Color {
|
||||||
if remaining == 0 {
|
if remaining == 0 {
|
||||||
return BLACK;
|
return BLACK;
|
||||||
@ -400,86 +122,6 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute refracted color for the given precomputation.
|
/// 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 {
|
pub fn refracted_color(&self, comps: &PrecomputedData, remaining: usize) -> Color {
|
||||||
if remaining == 0 {
|
if remaining == 0 {
|
||||||
return BLACK;
|
return BLACK;
|
||||||
@ -513,3 +155,406 @@ impl World {
|
|||||||
self.color_at(&refract_ray, remaining - 1) * comps.object.material.transparency
|
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<dyn std::error::Error>> {
|
||||||
|
// 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<dyn std::error::Error>> {
|
||||||
|
// 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<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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
// 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<dyn std::error::Error>> {
|
||||||
|
// 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<dyn std::error::Error>> {
|
||||||
|
// 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<dyn std::error::Error>> {
|
||||||
|
// 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user