From 135a519526027bb83fad8d690179badab1ea2504 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Fri, 30 Jul 2021 20:58:05 -0700 Subject: [PATCH] shapes: move tests from doctest to unit. --- rtchallenge/src/shapes.rs | 647 +++++++++++++++++++------------------- 1 file changed, 322 insertions(+), 325 deletions(-) diff --git a/rtchallenge/src/shapes.rs b/rtchallenge/src/shapes.rs index 6c359dd..cf384d6 100644 --- a/rtchallenge/src/shapes.rs +++ b/rtchallenge/src/shapes.rs @@ -57,64 +57,20 @@ pub struct Shape { } /// Short hand for creating a ShapeBuilder with a plane geometry. -/// -/// # Examples -/// ``` -/// use rtchallenge::shapes::{plane, Shape}; -/// # fn main() -> Result<(), Box> { -/// assert_eq!(plane().build()?, Shape::plane()); -/// # Ok(()) -/// # } -/// ``` pub fn plane() -> ShapeBuilder { ShapeBuilder::plane() } /// Short hand for creating a ShapeBuilder with a sphere geometry. -/// -/// # Examples -/// ``` -/// use rtchallenge::shapes::{sphere, Shape}; -/// -/// # fn main() -> Result<(), Box> { -/// assert_eq!(sphere().build()?, Shape::sphere()); -/// # Ok(()) -/// # } -/// ``` pub fn sphere() -> ShapeBuilder { ShapeBuilder::sphere() } /// Short hand for creating a ShapeBuilder with a test shape geometry. -/// -/// # Examples -/// ``` -/// use rtchallenge::shapes::{test_shape, Shape}; -/// -/// # fn main() -> Result<(), Box> { -/// assert_eq!(test_shape().build()?, Shape::test_shape()); -/// # Ok(()) -/// # } -/// ``` 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() @@ -126,44 +82,14 @@ pub fn glass_sphere() -> ShapeBuilder { } impl ShapeBuilder { /// Short hand for creating a ShapeBuilder with a plane geometry. - /// - /// # Examples - /// ``` - /// use rtchallenge::shapes::{plane, Shape}; - /// - /// # fn main() -> Result<(), Box> { - /// assert_eq!(plane().build()?, Shape::plane()); - /// # Ok(()) - /// # } - /// ``` pub fn plane() -> ShapeBuilder { ShapeBuilder::default().geometry(Geometry::Plane) } /// Short hand for creating a ShapeBuilder with a sphere geometry. - /// - /// # Examples - /// ``` - /// use rtchallenge::shapes::{sphere, Shape}; - /// - /// # fn main() -> Result<(), Box> { - /// assert_eq!(sphere().build()?, Shape::sphere()); - /// # Ok(()) - /// # } - /// ``` pub fn sphere() -> ShapeBuilder { ShapeBuilder::default().geometry(Geometry::Sphere) } /// Short hand for creating a ShapeBuilder with a test shape geometry. - /// - /// # Examples - /// ``` - /// use rtchallenge::shapes::{test_shape, Shape}; - /// - /// # fn main() -> Result<(), Box> { - /// assert_eq!(test_shape().build()?, Shape::test_shape()); - /// # Ok(()) - /// # } - /// ``` pub fn test_shape() -> ShapeBuilder { ShapeBuilder::default().geometry(Geometry::TestShape(Arc::new(Mutex::new( TestData::default(), @@ -187,24 +113,6 @@ impl Default for Shape { impl Shape { /// Create a test shape useful for debugging. - /// - /// # Examples - /// ``` - /// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape}; - /// - /// let mut s = Shape::test_shape(); - /// // The default transform. - /// assert_eq!(s.transform(), Matrix4x4::identity()); - /// // The default material. - /// assert_eq!(s.material, Material::default()); - /// // Assigning a material. - /// let mut m = Material { - /// ambient: 1., - /// ..Material::default() - /// }; - /// s.material = m.clone(); - /// assert_eq!(s.material, m); - /// ``` pub fn test_shape() -> Shape { Shape { transform: Matrix4x4::identity(), @@ -214,28 +122,6 @@ impl Shape { } } /// # Examples - /// ``` - /// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape}; - /// - /// // A sphere's default transform is the identity matrix. - /// let s = Shape::sphere(); - /// assert_eq!(s.transform(), Matrix4x4::identity()); - /// - /// // It can be changed by directly setting the transform member. - /// let mut s = Shape::sphere(); - /// let t = Matrix4x4::translation(2., 3., 4.); - /// s.set_transform(t.clone()); - /// assert_eq!(s.transform(), t); - /// - /// // Default Sphere has the default material. - /// assert_eq!(s.material, Material::default()); - /// // It can be overridden. - /// let mut s = Shape::sphere(); - /// let mut m = Material::default(); - /// m.ambient = 1.; - /// s.material = m.clone(); - /// assert_eq!(s.material, m); - /// ``` pub fn sphere() -> Shape { Shape { transform: Matrix4x4::identity(), @@ -253,100 +139,6 @@ impl Shape { } } /// Find the normal at the point on the sphere. - /// - /// # Examples - /// ``` - /// use rtchallenge::{ - /// float::consts::PI, materials::Material, matrices::Matrix4x4, shapes::Shape, tuples::Tuple, - /// Float, - /// }; - /// - /// // Computing the normal on a translated shape. - /// let mut s = Shape::test_shape(); - /// s.set_transform(Matrix4x4::translation(0., 1., 0.)); - /// let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711)); - /// assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711)); - /// - /// // Computing the normal on a transform shape. - /// let mut s = Shape::test_shape(); - /// s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.)); - /// let n = s.normal_at(Tuple::point( - /// 0., - /// (2. as Float).sqrt() / 2., - /// -(2. as Float).sqrt() / 2., - /// )); - /// assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254)); - /// - /// // Normal on X-axis - /// let s = Shape::sphere(); - /// let n = s.normal_at(Tuple::point(1., 0., 0.)); - /// assert_eq!(n, Tuple::vector(1., 0., 0.)); - /// - /// // Normal on Y-axis - /// let s = Shape::sphere(); - /// let n = s.normal_at(Tuple::point(0., 1., 0.)); - /// assert_eq!(n, Tuple::vector(0., 1., 0.)); - /// - /// // Normal on Z-axis - /// let s = Shape::sphere(); - /// let n = s.normal_at(Tuple::point(0., 0., 1.)); - /// assert_eq!(n, Tuple::vector(0., 0., 1.)); - /// - /// // Normal on a sphere at a nonaxial point. - /// let s = Shape::sphere(); - /// let n = s.normal_at(Tuple::point( - /// (3. as Float).sqrt() / 3., - /// (3. as Float).sqrt() / 3., - /// (3. as Float).sqrt() / 3., - /// )); - /// assert_eq!( - /// n, - /// Tuple::vector( - /// (3. as Float).sqrt() / 3., - /// (3. as Float).sqrt() / 3., - /// (3. as Float).sqrt() / 3., - /// ) - /// ); - /// // Normals returned are normalized. - /// let s = Shape::sphere(); - /// let n = s.normal_at(Tuple::point( - /// (3. as Float).sqrt() / 3., - /// (3. as Float).sqrt() / 3., - /// (3. as Float).sqrt() / 3., - /// )); - /// assert_eq!(n, n.normalize()); - /// - /// // Compute the normal on a translated sphere. - /// let mut s = Shape::sphere(); - /// s.set_transform(Matrix4x4::translation(0., 1., 0.)); - /// let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711)); - /// assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711)); - /// - /// // Compute the normal on a transformed sphere. - /// let mut s = Shape::sphere(); - /// s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.)); - /// let n = s.normal_at(Tuple::point( - /// 0., - /// (2. as Float).sqrt() / 2., - /// -(2. as Float).sqrt() / 2., - /// )); - /// assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254)); - /// - /// // Normal of a plane is constant everywhere. - /// let p = Shape::plane(); - /// assert_eq!( - /// p.normal_at(Tuple::point(0., 0., 0.)), - /// Tuple::vector(0., 1., 0.) - /// ); - /// assert_eq!( - /// p.normal_at(Tuple::point(10., 0., -10.)), - /// Tuple::vector(0., 1., 0.) - /// ); - /// assert_eq!( - /// p.normal_at(Tuple::point(-5., 0., 150.)), - /// Tuple::vector(0., 1., 0.) - /// ); - /// ``` pub fn normal_at(&self, world_point: Tuple) -> Tuple { let object_point = self.inverse_transform * world_point; let object_normal = match self.geometry { @@ -377,123 +169,6 @@ impl Shape { } /// Intersect a ray with a shapes. -/// -/// # Examples -/// ``` -/// use rtchallenge::{ -/// intersections::{Intersection, Intersections}, -/// matrices::Matrix4x4, -/// rays::Ray, -/// shapes::{intersect, Geometry, Shape}, -/// tuples::Tuple, -/// }; -/// -/// // Intersecting a scaled shape with a ray. -/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); -/// let mut s = Shape::test_shape(); -/// s.set_transform(Matrix4x4::scaling(2., 2., 2.)); -/// let xs = intersect(&s, &r); -/// if let Geometry::TestShape(data) = s.geometry() { -/// if let Some(ray) = &data.lock().unwrap().saved_ray { -/// assert_eq!(ray.origin, Tuple::point(0., 0., -2.5)); -/// assert_eq!(ray.direction, Tuple::vector(0., 0., 0.5)); -/// } else { -/// panic!("ray wasn't set"); -/// }; -/// } else { -/// panic!("test_shape returned a non-TestShape geometry") -/// }; -/// -/// // Intersecting a translated shape with a ray. -/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); -/// let mut s = Shape::test_shape(); -/// s.set_transform(Matrix4x4::translation(5., 0., 0.)); -/// let xs = intersect(&s, &r); -/// if let Geometry::TestShape(data) = s.geometry() { -/// if let Some(ray) = &data.lock().unwrap().saved_ray { -/// assert_eq!(ray.origin, Tuple::point(-5., 0., -5.)); -/// assert_eq!(ray.direction, Tuple::vector(0., 0., 1.)); -/// } else { -/// panic!("ray wasn't set"); -/// }; -/// } else { -/// panic!("test_shape returned a non-TestShape geometry") -/// }; -/// -/// // A ray intersects a sphere in two points. -/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); -/// let s = Shape::sphere(); -/// let xs = intersect(&s, &r); -/// assert_eq!( -/// xs, -/// Intersections::new(vec![Intersection::new(4., &s), Intersection::new(6., &s)]) -/// ); -/// -/// // A ray intersects a sphere at a tangent. -/// let r = Ray::new(Tuple::point(0., 2., -5.), Tuple::vector(0., 0., 1.)); -/// let s = Shape::sphere(); -/// let xs = intersect(&s, &r); -/// assert_eq!(xs, Intersections::default()); -/// -/// // A ray originates inside a sphere. -/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.)); -/// let s = Shape::sphere(); -/// let xs = intersect(&s, &r); -/// assert_eq!( -/// xs, -/// Intersections::new(vec![Intersection::new(-1., &s), Intersection::new(1., &s)]) -/// ); -/// -/// // A sphere is behind a ray. -/// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.)); -/// let s = Shape::sphere(); -/// let xs = intersect(&s, &r); -/// assert_eq!( -/// xs, -/// Intersections::new(vec![Intersection::new(-6., &s), Intersection::new(-4., &s)]) -/// ); -/// -/// // Intersect a scaled sphere with a ray. -/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); -/// let mut s = Shape::sphere(); -/// s.set_transform(Matrix4x4::scaling(2., 2., 2.)); -/// let xs = intersect(&s, &r); -/// assert_eq!(xs.len(), 2, "xs {:?}", xs); -/// assert_eq!(xs[0].t, 3., "xs {:?}", xs); -/// assert_eq!(xs[1].t, 7., "xs {:?}", xs); -/// -/// // Intersect a translated sphere with a ray. -/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); -/// let mut s = Shape::sphere(); -/// s.set_transform(Matrix4x4::translation(5., 0., 0.)); -/// let xs = intersect(&s, &r); -/// assert_eq!(xs.len(), 0); -/// -/// // Intersect with a ray parallel to the plane. -/// let p = Shape::plane(); -/// let r = Ray::new(Tuple::point(0., 10., 0.), Tuple::vector(0., 0., 1.)); -/// let xs = intersect(&p, &r); -/// assert_eq!(xs.len(), 0); -/// -/// // Intersect with a coplanar. -/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.)); -/// let xs = intersect(&p, &r); -/// assert_eq!(xs.len(), 0); -/// -/// // A ray intersecting a plane from above. -/// let r = Ray::new(Tuple::point(0., 1., 0.), Tuple::vector(0., -1., 0.)); -/// let xs = intersect(&p, &r); -/// assert_eq!(xs.len(), 1); -/// assert_eq!(xs[0].t, 1.); -/// assert_eq!(xs[0].object, &p); -/// -/// // A ray intersecting a plane from below. -/// let r = Ray::new(Tuple::point(0., -1., 0.), Tuple::vector(0., 1., 0.)); -/// let xs = intersect(&p, &r); -/// assert_eq!(xs.len(), 1); -/// assert_eq!(xs[0].t, 1.); -/// assert_eq!(xs[0].object, &p); -/// ``` pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> { let local_ray = ray.transform(shape.inverse_transform); match shape.geometry { @@ -559,3 +234,325 @@ mod plane { )]) } } + +#[cfg(test)] +mod tests { + mod shape_builder { + use std::error::Error; + + use crate::shapes::{plane, sphere, test_shape, Shape}; + + #[test] + fn plane_builder() -> Result<(), Box> { + assert_eq!(plane().build()?, Shape::plane()); + Ok(()) + } + #[test] + fn sphere_builder() -> Result<(), Box> { + assert_eq!(sphere().build()?, Shape::sphere()); + Ok(()) + } + #[test] + fn test_shape_builder() -> Result<(), Box> { + assert_eq!(test_shape().build()?, Shape::test_shape()); + Ok(()) + } + } + mod shape { + use crate::{ + materials::Material, + matrices::{identity, translation}, + shapes::Shape, + }; + + #[test] + fn test_shape() { + let mut s = Shape::test_shape(); + // The default transform. + assert_eq!(s.transform(), identity()); + // The default material. + assert_eq!(s.material, Material::default()); + // Assigning a material. + let m = Material { + ambient: 1., + ..Material::default() + }; + s.material = m.clone(); + assert_eq!(s.material, m); + } + #[test] + fn sphere() { + // A sphere's default transform is the identity matrix. + let s = Shape::sphere(); + assert_eq!(s.transform(), identity()); + + // It can be changed by directly setting the transform member. + let mut s = Shape::sphere(); + let t = translation(2., 3., 4.); + s.set_transform(t.clone()); + assert_eq!(s.transform(), t); + + // Default Sphere has the default material. + assert_eq!(s.material, Material::default()); + // It can be overridden. + let mut s = Shape::sphere(); + let mut m = Material::default(); + m.ambient = 1.; + s.material = m.clone(); + assert_eq!(s.material, m); + } + } + mod normal_at { + use crate::{float::consts::PI, matrices::Matrix4x4, shapes::Shape, tuples::Tuple, Float}; + #[test] + fn compute_normal_on_translated_shape() { + // Computing the normal on a translated shape. + let mut s = Shape::test_shape(); + s.set_transform(Matrix4x4::translation(0., 1., 0.)); + let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711)); + assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711)); + } + #[test] + fn compute_normal_on_scaled_shape() { + // Computing the normal on a scaled shape. + let mut s = Shape::test_shape(); + s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.)); + let n = s.normal_at(Tuple::point( + 0., + (2. as Float).sqrt() / 2., + -(2. as Float).sqrt() / 2., + )); + assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254)); + } + #[test] + fn sphere_normal_on_x_axis() { + // Normal on X-axis + let s = Shape::sphere(); + let n = s.normal_at(Tuple::point(1., 0., 0.)); + assert_eq!(n, Tuple::vector(1., 0., 0.)); + } + #[test] + fn sphere_normal_on_y_axis() { + // Normal on Y-axis + let s = Shape::sphere(); + let n = s.normal_at(Tuple::point(0., 1., 0.)); + assert_eq!(n, Tuple::vector(0., 1., 0.)); + } + #[test] + fn sphere_normal_on_z_axis() { + // Normal on Z-axis + let s = Shape::sphere(); + let n = s.normal_at(Tuple::point(0., 0., 1.)); + assert_eq!(n, Tuple::vector(0., 0., 1.)); + } + #[test] + fn sphere_normal_non_axial() { + // Normal on a sphere at a nonaxial point. + let s = Shape::sphere(); + let n = s.normal_at(Tuple::point( + (3. as Float).sqrt() / 3., + (3. as Float).sqrt() / 3., + (3. as Float).sqrt() / 3., + )); + assert_eq!( + n, + Tuple::vector( + (3. as Float).sqrt() / 3., + (3. as Float).sqrt() / 3., + (3. as Float).sqrt() / 3., + ) + ); + } + #[test] + fn normals_are_normalized() { + // Normals returned are normalized. + let s = Shape::sphere(); + let n = s.normal_at(Tuple::point( + (3. as Float).sqrt() / 3., + (3. as Float).sqrt() / 3., + (3. as Float).sqrt() / 3., + )); + assert_eq!(n, n.normalize()); + } + #[test] + fn compute_normal_on_translated_sphere() { + // Compute the normal on a translated sphere. + let mut s = Shape::sphere(); + s.set_transform(Matrix4x4::translation(0., 1., 0.)); + let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711)); + assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711)); + } + #[test] + fn compute_normal_on_scaled_sphere() { + // Compute the normal on a transformed sphere. + let mut s = Shape::sphere(); + s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.)); + let n = s.normal_at(Tuple::point( + 0., + (2. as Float).sqrt() / 2., + -(2. as Float).sqrt() / 2., + )); + assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254)); + } + #[test] + fn nomal_of_plane_constant() { + // Normal of a plane is constant everywhere. + let p = Shape::plane(); + assert_eq!( + p.normal_at(Tuple::point(0., 0., 0.)), + Tuple::vector(0., 1., 0.) + ); + assert_eq!( + p.normal_at(Tuple::point(10., 0., -10.)), + Tuple::vector(0., 1., 0.) + ); + assert_eq!( + p.normal_at(Tuple::point(-5., 0., 150.)), + Tuple::vector(0., 1., 0.) + ); + } + } + mod intersect { + use crate::{ + intersections::{Intersection, Intersections}, + matrices::Matrix4x4, + rays::Ray, + shapes::{intersect, Geometry, Shape}, + tuples::Tuple, + }; + + #[test] + fn scaled_shape_with_ray() { + // Intersecting a scaled shape with a ray. + let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); + let mut s = Shape::test_shape(); + s.set_transform(Matrix4x4::scaling(2., 2., 2.)); + let xs = intersect(&s, &r); + if let Geometry::TestShape(data) = s.geometry() { + if let Some(ray) = &data.lock().unwrap().saved_ray { + assert_eq!(ray.origin, Tuple::point(0., 0., -2.5)); + assert_eq!(ray.direction, Tuple::vector(0., 0., 0.5)); + } else { + panic!("ray wasn't set"); + }; + } else { + panic!("test_shape returned a non-TestShape geometry") + }; + } + #[test] + fn translated_shape_with_ray() { + // Intersecting a translated shape with a ray. + let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); + let mut s = Shape::test_shape(); + s.set_transform(Matrix4x4::translation(5., 0., 0.)); + let xs = intersect(&s, &r); + if let Geometry::TestShape(data) = s.geometry() { + if let Some(ray) = &data.lock().unwrap().saved_ray { + assert_eq!(ray.origin, Tuple::point(-5., 0., -5.)); + assert_eq!(ray.direction, Tuple::vector(0., 0., 1.)); + } else { + panic!("ray wasn't set"); + }; + } else { + panic!("test_shape returned a non-TestShape geometry") + }; + } + #[test] + fn ray_intersects_sphere_two_points() { + // A ray intersects a sphere in two points. + let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); + let s = Shape::sphere(); + let xs = intersect(&s, &r); + assert_eq!( + xs, + Intersections::new(vec![Intersection::new(4., &s), Intersection::new(6., &s)]) + ); + } + #[test] + fn ray_intersects_at_tangent() { + // A ray intersects a sphere at a tangent. + let r = Ray::new(Tuple::point(0., 2., -5.), Tuple::vector(0., 0., 1.)); + let s = Shape::sphere(); + let xs = intersect(&s, &r); + assert_eq!(xs, Intersections::default()); + } + #[test] + fn ray_originates_inside_sphere() { + // A ray originates inside a sphere. + let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.)); + let s = Shape::sphere(); + let xs = intersect(&s, &r); + assert_eq!( + xs, + Intersections::new(vec![Intersection::new(-1., &s), Intersection::new(1., &s)]) + ); + } + #[test] + fn sphere_behind_ray() { + // A sphere is behind a ray. + let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.)); + let s = Shape::sphere(); + let xs = intersect(&s, &r); + assert_eq!( + xs, + Intersections::new(vec![Intersection::new(-6., &s), Intersection::new(-4., &s)]) + ); + } + #[test] + fn ray_intersects_scaled_sphere() { + // Intersect a scaled sphere with a ray. + let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); + let mut s = Shape::sphere(); + s.set_transform(Matrix4x4::scaling(2., 2., 2.)); + let xs = intersect(&s, &r); + assert_eq!(xs.len(), 2, "xs {:?}", xs); + assert_eq!(xs[0].t, 3., "xs {:?}", xs); + assert_eq!(xs[1].t, 7., "xs {:?}", xs); + } + #[test] + fn ray_intersects_translated_sphere() { + // Intersect a translated sphere with a ray. + let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.)); + let mut s = Shape::sphere(); + s.set_transform(Matrix4x4::translation(5., 0., 0.)); + let xs = intersect(&s, &r); + assert_eq!(xs.len(), 0); + } + #[test] + fn ray_parallel_to_plane() { + // Intersect with a ray parallel to the plane. + let p = Shape::plane(); + let r = Ray::new(Tuple::point(0., 10., 0.), Tuple::vector(0., 0., 1.)); + let xs = intersect(&p, &r); + assert_eq!(xs.len(), 0); + } + #[test] + fn ray_coplanar_to_plane() { + // Intersect with a coplanar. + let p = Shape::plane(); + let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.)); + let xs = intersect(&p, &r); + assert_eq!(xs.len(), 0); + } + #[test] + fn ray_intersects_plane_from_above() { + // A ray intersecting a plane from above. + let p = Shape::plane(); + let r = Ray::new(Tuple::point(0., 1., 0.), Tuple::vector(0., -1., 0.)); + let xs = intersect(&p, &r); + assert_eq!(xs.len(), 1); + assert_eq!(xs[0].t, 1.); + assert_eq!(xs[0].object, &p); + } + #[test] + fn ray_intersects_plane_from_below() { + // A ray intersecting a plane from below. + let p = Shape::plane(); + let r = Ray::new(Tuple::point(0., -1., 0.), Tuple::vector(0., 1., 0.)); + let xs = intersect(&p, &r); + assert_eq!(xs.len(), 1); + assert_eq!(xs[0].t, 1.); + assert_eq!(xs[0].object, &p); + } + } +}