shapes: move tests from doctest to unit.
This commit is contained in:
parent
5d6b3e6d57
commit
135a519526
@ -57,64 +57,20 @@ pub struct Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{plane, Shape};
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(plane().build()?, Shape::plane());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn plane() -> ShapeBuilder {
|
pub fn plane() -> ShapeBuilder {
|
||||||
ShapeBuilder::plane()
|
ShapeBuilder::plane()
|
||||||
}
|
}
|
||||||
/// Short hand for creating a ShapeBuilder with a sphere geometry.
|
/// Short hand for creating a ShapeBuilder with a sphere geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{sphere, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(sphere().build()?, Shape::sphere());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn sphere() -> ShapeBuilder {
|
pub fn sphere() -> ShapeBuilder {
|
||||||
ShapeBuilder::sphere()
|
ShapeBuilder::sphere()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{test_shape, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(test_shape().build()?, Shape::test_shape());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn test_shape() -> ShapeBuilder {
|
pub fn test_shape() -> ShapeBuilder {
|
||||||
ShapeBuilder::test_shape()
|
ShapeBuilder::test_shape()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for producing a sphere with a glassy material.
|
/// Helper for producing a sphere with a glassy material.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{matrices::identity, shapes::glass_sphere};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
///
|
|
||||||
/// let s = glass_sphere().build()?;
|
|
||||||
/// assert_eq!(s.transform(), identity());
|
|
||||||
/// assert_eq!(s.material.transparency, 1.);
|
|
||||||
/// assert_eq!(s.material.refractive_index, 1.5);
|
|
||||||
///
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn glass_sphere() -> ShapeBuilder {
|
pub fn glass_sphere() -> ShapeBuilder {
|
||||||
ShapeBuilder::sphere().material(
|
ShapeBuilder::sphere().material(
|
||||||
MaterialBuilder::default()
|
MaterialBuilder::default()
|
||||||
@ -126,44 +82,14 @@ pub fn glass_sphere() -> ShapeBuilder {
|
|||||||
}
|
}
|
||||||
impl ShapeBuilder {
|
impl ShapeBuilder {
|
||||||
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{plane, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(plane().build()?, Shape::plane());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn plane() -> ShapeBuilder {
|
pub fn plane() -> ShapeBuilder {
|
||||||
ShapeBuilder::default().geometry(Geometry::Plane)
|
ShapeBuilder::default().geometry(Geometry::Plane)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a ShapeBuilder with a sphere geometry.
|
/// Short hand for creating a ShapeBuilder with a sphere geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{sphere, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(sphere().build()?, Shape::sphere());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn sphere() -> ShapeBuilder {
|
pub fn sphere() -> ShapeBuilder {
|
||||||
ShapeBuilder::default().geometry(Geometry::Sphere)
|
ShapeBuilder::default().geometry(Geometry::Sphere)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{test_shape, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(test_shape().build()?, Shape::test_shape());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn test_shape() -> ShapeBuilder {
|
pub fn test_shape() -> ShapeBuilder {
|
||||||
ShapeBuilder::default().geometry(Geometry::TestShape(Arc::new(Mutex::new(
|
ShapeBuilder::default().geometry(Geometry::TestShape(Arc::new(Mutex::new(
|
||||||
TestData::default(),
|
TestData::default(),
|
||||||
@ -187,24 +113,6 @@ impl Default for Shape {
|
|||||||
|
|
||||||
impl Shape {
|
impl Shape {
|
||||||
/// Create a test shape useful for debugging.
|
/// 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 {
|
pub fn test_shape() -> Shape {
|
||||||
Shape {
|
Shape {
|
||||||
transform: Matrix4x4::identity(),
|
transform: Matrix4x4::identity(),
|
||||||
@ -214,28 +122,6 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// # Examples
|
/// # 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 {
|
pub fn sphere() -> Shape {
|
||||||
Shape {
|
Shape {
|
||||||
transform: Matrix4x4::identity(),
|
transform: Matrix4x4::identity(),
|
||||||
@ -253,100 +139,6 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Find the normal at the point on the sphere.
|
/// 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 {
|
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
|
||||||
let object_point = self.inverse_transform * world_point;
|
let object_point = self.inverse_transform * world_point;
|
||||||
let object_normal = match self.geometry {
|
let object_normal = match self.geometry {
|
||||||
@ -377,123 +169,6 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Intersect a ray with a shapes.
|
/// 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> {
|
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
||||||
let local_ray = ray.transform(shape.inverse_transform);
|
let local_ray = ray.transform(shape.inverse_transform);
|
||||||
match shape.geometry {
|
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<dyn Error>> {
|
||||||
|
assert_eq!(plane().build()?, Shape::plane());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sphere_builder() -> Result<(), Box<dyn Error>> {
|
||||||
|
assert_eq!(sphere().build()?, Shape::sphere());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_shape_builder() -> Result<(), Box<dyn Error>> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user