Bill Thiede e574cdb592
Some checks failed
continuous-integration/drone Build is failing
Random changes.
2022-06-11 17:46:26 -07:00

827 lines
28 KiB
Rust

use std::sync::{Arc, Mutex};
use derive_builder::Builder;
use crate::{
intersections::Intersections,
materials::{Material, MaterialBuilder},
matrices::{identity, Matrix4x4},
rays::Ray,
tuples::Tuple,
};
#[derive(Default, PartialEq, Debug, Clone)]
pub struct TestData {
pub saved_ray: Option<Ray>,
}
#[derive(Debug, Clone)]
pub enum Geometry {
/// Shape with predictable normals useful for debugging.
TestShape(Arc<Mutex<TestData>>),
/// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
Sphere,
/// Flat surface that extends infinitely in the XZ axes.
Plane,
/// AABB cube at origin from -1,1 in each direction.
Cube,
/// Takes shape from children held within.
Group(Vec<Shape>),
}
impl Default for Geometry {
fn default() -> Geometry {
Geometry::Sphere
}
}
impl PartialEq for Geometry {
fn eq(&self, rhs: &Geometry) -> bool {
use Geometry::*;
match (self, rhs) {
(TestShape(l), TestShape(r)) => *l.lock().unwrap() == *r.lock().unwrap(),
(Sphere, Sphere) => true,
(Plane, Plane) => true,
(Cube, Cube) => true,
(Group(v1), Group(v2)) => v1 == v2,
_ => false,
}
}
}
/// Shape represents visible objects. A signal instance of Shape can generically represent one of
/// many different shapes based on the value of it's geometry field. Users chose the shape by
/// calling the appropriate constructor, i.e. [Shape::sphere].
#[derive(Builder, Debug, Clone, PartialEq)]
#[builder(default, pattern = "owned", build_fn(skip))]
pub struct Shape {
transform: Matrix4x4,
inverse_transform: Matrix4x4,
pub material: Material,
geometry: Geometry,
}
/// Short hand for creating a ShapeBuilder with a plane geometry.
pub fn plane() -> ShapeBuilder {
ShapeBuilder::plane()
}
/// Short hand for creating a ShapeBuilder with a sphere geometry.
pub fn sphere() -> ShapeBuilder {
ShapeBuilder::sphere()
}
/// Short hand for creating a ShapeBuilder with a test shape geometry.
pub fn test_shape() -> ShapeBuilder {
ShapeBuilder::test_shape()
}
/// Short hand for creating a ShapeBuilder with a cube geometry.
pub fn cube() -> ShapeBuilder {
ShapeBuilder::cube()
}
/// Short hand for creating a ShapeBuilder with a group geometry.
pub fn group() -> ShapeBuilder {
ShapeBuilder::group()
}
/// Helper for producing a sphere with a glassy material.
pub fn glass_sphere() -> ShapeBuilder {
ShapeBuilder::sphere().material(
MaterialBuilder::default()
.transparency(1.)
.refractive_index(1.5)
.build()
.unwrap(),
)
}
impl ShapeBuilder {
/// Short hand for creating a ShapeBuilder with a plane geometry.
pub fn plane() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::Plane)
}
/// Short hand for creating a ShapeBuilder with a sphere geometry.
pub fn sphere() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::Sphere)
}
/// Short hand for creating a ShapeBuilder with a test shape geometry.
pub fn test_shape() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::TestShape(Arc::new(Mutex::new(
TestData::default(),
))))
}
/// Short hand for creating a ShapeBuilder with a cube geometry.
pub fn cube() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::Cube)
}
/// Short hand for creating a ShapeBuilder with a group geometry.
pub fn group() -> ShapeBuilder {
ShapeBuilder::default().geometry(Geometry::Group(Vec::new()))
}
/// Add child shapes, only valid for Group::Geometry.
pub fn add_child(mut self, child: Shape) -> ShapeBuilder {
if let Some(Geometry::Group(ref mut children)) = &mut self.geometry {
children.push(child);
}
self
}
pub fn build(&self) -> Result<Shape, ShapeBuilderError> {
let mut s = Shape::default();
if let Some(transform) = &self.transform {
s.set_transform(transform.clone());
}
if let Some(material) = &self.material {
s.material = material.clone();
};
if let Some(geometry) = &self.geometry {
s.geometry = geometry.clone();
};
let transform = s.transform().clone();
if let Geometry::Group(ref mut children) = s.geometry {
children
.into_iter()
.for_each(|c| c.set_transform(transform * c.transform()));
}
Ok(s)
}
}
impl Default for Shape {
fn default() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::default(),
}
}
}
impl Shape {
/// Create a test shape useful for debugging.
pub fn test_shape() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::TestShape(Arc::new(Mutex::new(TestData::default()))),
}
}
pub fn sphere() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::Sphere,
}
}
pub fn plane() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::Plane,
}
}
pub fn cube() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::Cube,
}
}
pub fn group() -> Shape {
Shape {
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
material: Material::default(),
geometry: Geometry::Group(Vec::new()),
}
}
/// Find the normal at the point on the sphere.
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
let object_point = self.inverse_transform * world_point;
let object_normal = match self.geometry {
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
Geometry::Plane => Tuple::vector(0., 1., 0.),
Geometry::TestShape(_) => object_point,
Geometry::Cube => cube::local_normal_at(object_point),
Geometry::Group(_) => todo!("normal_at"),
};
let mut world_normal = self.inverse_transform.transpose() * object_normal;
world_normal.w = 0.;
world_normal.normalize()
}
pub fn transform(&self) -> Matrix4x4 {
self.transform
}
pub fn inverse_transform(&self) -> Matrix4x4 {
self.inverse_transform
}
pub fn set_transform(&mut self, t: Matrix4x4) {
self.transform = t;
self.inverse_transform = t.inverse();
}
pub fn geometry(&self) -> &Geometry {
&self.geometry
}
}
/// Intersect a ray with a shapes.
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
let local_ray = ray.transform(shape.inverse_transform);
match shape.geometry {
Geometry::Sphere => sphere::intersect(shape, &local_ray),
Geometry::Plane => plane::intersect(shape, &local_ray),
Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray),
Geometry::Cube => cube::intersect(shape, &local_ray),
Geometry::Group(_) => group::intersect(shape, &ray),
}
}
mod test_shape {
use crate::{
intersections::Intersections,
rays::Ray,
shapes::{Geometry, Shape},
};
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
if let Geometry::TestShape(s) = &shape.geometry {
s.lock()
.expect("couldn't grab mutex for TestData")
.saved_ray = Some(ray.clone());
}
Intersections::default()
}
}
mod sphere {
use crate::{
intersections::{Intersection, Intersections},
rays::Ray,
shapes::Shape,
tuples::{dot, Tuple},
};
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
let sphere_to_ray = ray.origin - Tuple::point(0., 0., 0.);
let a = dot(ray.direction, ray.direction);
let b = 2. * dot(ray.direction, sphere_to_ray);
let c = dot(sphere_to_ray, sphere_to_ray) - 1.;
let discriminant = b * b - 4. * a * c;
if discriminant < 0. {
return Intersections::default();
}
Intersections::new(vec![
Intersection::new((-b - discriminant.sqrt()) / (2. * a), &shape),
Intersection::new((-b + discriminant.sqrt()) / (2. * a), &shape),
])
}
}
mod plane {
use crate::{
intersections::{Intersection, Intersections},
rays::Ray,
shapes::Shape,
EPSILON,
};
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
if (ray.direction.y).abs() < EPSILON {
return Intersections::default();
}
Intersections::new(vec![Intersection::new(
-ray.origin.y / ray.direction.y,
&shape,
)])
}
}
mod cube {
use crate::{
intersections::{Intersection, Intersections},
rays::Ray,
shapes::Shape,
tuples::{vector, Tuple},
Float, EPSILON,
};
fn check_axis(origin: Float, direction: Float) -> (Float, Float) {
let tmin_numerator = -1. - origin;
let tmax_numerator = 1. - origin;
let (tmin, tmax) = if direction.abs() >= EPSILON {
(tmin_numerator / direction, tmax_numerator / direction)
} else {
(
tmin_numerator * Float::INFINITY,
tmax_numerator * Float::INFINITY,
)
};
if tmin > tmax {
return (tmax, tmin);
}
(tmin, tmax)
}
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
let (xtmin, xtmax) = check_axis(ray.origin.x, ray.direction.x);
let (ytmin, ytmax) = check_axis(ray.origin.y, ray.direction.y);
let (ztmin, ztmax) = check_axis(ray.origin.z, ray.direction.z);
let tmin = xtmin.max(ytmin).max(ztmin);
let tmax = xtmax.min(ytmax).min(ztmax);
if tmin > tmax {
return Intersections::default();
}
Intersections::new(vec![
Intersection::new(tmin, &shape),
Intersection::new(tmax, &shape),
])
}
pub fn local_normal_at(point: Tuple) -> Tuple {
let x = point.x.abs();
let y = point.y.abs();
let z = point.z.abs();
let maxc = x.max(y).max(z);
if maxc == x {
return vector(point.x, 0., 0.);
}
if maxc == y {
return vector(0., point.y, 0.);
}
vector(0., 0., point.z)
}
#[cfg(test)]
mod tests {
use crate::{
rays::Ray,
shapes::Shape,
tuples::{point, vector},
EPSILON,
};
use super::{intersect, local_normal_at};
#[test]
fn ray_intersects_cube() {
let c = Shape::cube();
for (name, o, d, t1, t2) in [
("+x", point(5., 0.5, 0.), vector(-1., 0., 0.), 4., 6.),
("-x", point(-5., 0.5, 0.), vector(1., 0., 0.), 4., 6.),
("+y", point(0.5, 5., 0.), vector(0., -1., 0.), 4., 6.),
("-y", point(0.5, -5., 0.), vector(0., 1., 0.), 4., 6.),
("+z", point(0.5, 0., 5.), vector(0., 0., -1.), 4., 6.),
("-z", point(0.5, 0., -5.), vector(0., 0., 1.), 4., 6.),
("inside", point(0., 0.5, 0.), vector(0., 0., 1.), -1., 1.),
] {
let r = Ray::new(o, d);
let xs = intersect(&c, &r);
assert_eq!(xs.len(), 2, "{}", name);
assert!((xs[0].t - t1).abs() < EPSILON, "{} t1 {}", name, xs[0].t);
assert!((xs[1].t - t2).abs() < EPSILON, "{} t2 {}", name, xs[1].t);
}
}
#[test]
fn ray_misses_cube() {
let c = Shape::cube();
for (o, d) in [
(point(-2., 0., 0.), vector(0.2673, 0.5345, 0.8018)),
(point(0., -2., 0.), vector(0.8018, 0.2673, 0.5345)),
(point(0., 0., -2.), vector(0.5345, 0.8018, 0.2673)),
(point(2., 0., 2.), vector(0., 0., -1.)),
(point(0., 2., 2.), vector(0., -1., 0.)),
(point(2., 2., -5.), vector(-1., 0., 0.)),
] {
let r = Ray::new(o, d);
let xs = intersect(&c, &r);
assert_eq!(xs.len(), 0, "({:?}, {:?})", o, d);
}
}
#[test]
fn normal_cube_surface() {
for (p, n) in [
(point(1., 0.8, -0.8), vector(1., 0., 0.)),
(point(-1., -0.2, 0.9), vector(-1., 0., 0.)),
(point(-0.4, 1., -0.1), vector(0., 1., 0.)),
(point(0.3, -1., -0.7), vector(0., -1., 0.)),
(point(-0.6, 0.3, 1.), vector(0., 0., 1.)),
(point(0.4, 0.4, -1.), vector(0., 0., -1.)),
(point(1., 1., 1.), vector(1., 0., 0.)),
(point(-1., -1., -1.), vector(-1., 0., 0.)),
] {
let normal = local_normal_at(p);
assert_eq!(n, normal);
}
}
}
}
mod group {
use crate::{intersections::Intersections, rays::Ray, shapes::Shape};
use super::Geometry;
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
if let Geometry::Group(children) = &shape.geometry {
let mut intersections: Vec<_> = children
.iter()
.map(|c| super::intersect(c, ray))
.flatten()
.collect();
intersections.sort_by(|a, b| {
a.t.partial_cmp(&b.t)
.expect("an intersection has a t value that is NaN")
});
return Intersections::new(intersections);
}
unreachable!();
}
#[cfg(test)]
mod tests {
use crate::{
matrices::{scaling, translation},
rays::Ray,
shapes::{intersect, Shape, ShapeBuilder},
tuples::{point, vector},
};
#[test]
fn intersecting_empty_group() {
let g = Shape::group();
let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.));
let xs = intersect(&g, &r);
assert_eq!(xs.len(), 0);
}
#[test]
fn intersecting_nonempty_group() -> Result<(), Box<dyn std::error::Error>> {
let s1 = Shape::sphere();
let s2 = ShapeBuilder::sphere()
.transform(translation(0., 0., -3.))
.build()?;
let s3 = ShapeBuilder::sphere()
.transform(translation(5., 0., 0.))
.build()?;
let g = ShapeBuilder::group()
.add_child(s1.clone())
.add_child(s2.clone())
.add_child(s3.clone())
.build()?;
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
let xs = intersect(&g, &r);
assert_eq!(xs.len(), 4);
assert_eq!(xs[0].object, &s2);
assert_eq!(xs[1].object, &s2);
assert_eq!(xs[2].object, &s1);
assert_eq!(xs[3].object, &s1);
Ok(())
}
#[test]
fn intersecting_transformed_group() -> Result<(), Box<dyn std::error::Error>> {
let g = ShapeBuilder::group()
.transform(scaling(2., 2., 2.))
.add_child(
ShapeBuilder::sphere()
.transform(translation(5., 0., 0.))
.build()?,
)
.build()?;
let r = Ray::new(point(10., 0., -10.), vector(0., 0., 1.));
let xs = intersect(&g, &r);
assert_eq!(xs.len(), 2);
Ok(())
}
}
}
#[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);
}
}
}