intersections: move tests from doctest to unit.
This commit is contained in:
parent
42e8ebe3bd
commit
5debb16d10
@ -79,50 +79,6 @@ impl<'i> Intersections<'i> {
|
||||
self.0.len()
|
||||
}
|
||||
/// Finds nearest hit for this collection of intersections.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{Intersection, Intersections},
|
||||
/// rays::Ray,
|
||||
/// shapes::{intersect, Shape},
|
||||
/// tuples::Tuple,
|
||||
/// };
|
||||
///
|
||||
/// // The hit, when all intersections have positive t.
|
||||
/// let s = Shape::sphere();
|
||||
/// let i1 = Intersection::new(1., &s);
|
||||
/// let i2 = Intersection::new(2., &s);
|
||||
/// let xs = Intersections::new(vec![i2, i1.clone()]);
|
||||
/// let i = xs.hit();
|
||||
/// assert_eq!(i, Some(&i1));
|
||||
///
|
||||
/// // The hit, when some intersections have negative t.
|
||||
/// let s = Shape::sphere();
|
||||
/// let i1 = Intersection::new(-1., &s);
|
||||
/// let i2 = Intersection::new(1., &s);
|
||||
/// let xs = Intersections::new(vec![i2.clone(), i1]);
|
||||
/// let i = xs.hit();
|
||||
/// assert_eq!(i, Some(&i2));
|
||||
///
|
||||
/// // The hit, when all intersections have negative t.
|
||||
/// let s = Shape::sphere();
|
||||
/// let i1 = Intersection::new(-2., &s);
|
||||
/// let i2 = Intersection::new(-1., &s);
|
||||
/// let xs = Intersections::new(vec![i2, i1]);
|
||||
/// let i = xs.hit();
|
||||
/// assert_eq!(i, None);
|
||||
///
|
||||
/// // The hit is always the lowest nonnegative intersection.
|
||||
/// let s = Shape::sphere();
|
||||
/// let i1 = Intersection::new(5., &s);
|
||||
/// let i2 = Intersection::new(7., &s);
|
||||
/// let i3 = Intersection::new(-3., &s);
|
||||
/// let i4 = Intersection::new(2., &s);
|
||||
/// let xs = Intersections::new(vec![i1, i2, i3, i4.clone()]);
|
||||
/// let i = xs.hit();
|
||||
/// assert_eq!(i, Some(&i4));
|
||||
/// ```
|
||||
pub fn hit(&self) -> Option<&Intersection> {
|
||||
self.0.iter().filter(|i| i.t > 0.).min_by(|i1, i2| {
|
||||
i1.t.partial_cmp(&i2.t)
|
||||
@ -163,102 +119,6 @@ pub struct PrecomputedData<'i> {
|
||||
}
|
||||
|
||||
/// Precomputes data common to all intersections.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{prepare_computations, Intersection, Intersections},
|
||||
/// rays::Ray,
|
||||
/// materials::MaterialBuilder,
|
||||
/// matrices::{Matrix4x4,scaling,translation},
|
||||
/// shapes::{intersect, Shape,glass_sphere},
|
||||
/// tuples::{point,vector,Tuple},
|
||||
/// EPSILON,Float
|
||||
/// };
|
||||
///
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
///
|
||||
/// // Precomputing the state of an intersection.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let shape = Shape::sphere();
|
||||
/// let xs = Intersections::from(Intersection::new(4., &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert_eq!(comps.t, xs[0].t);
|
||||
/// assert_eq!(comps.object, xs[0].object);
|
||||
/// assert_eq!(comps.point, Tuple::point(0., 0., -1.));
|
||||
/// assert_eq!(comps.eyev, Tuple::vector(0., 0., -1.));
|
||||
/// assert_eq!(comps.normalv, Tuple::vector(0., 0., -1.));
|
||||
///
|
||||
/// // The hit, when an intersection occurs on the outside.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let shape = Shape::sphere();
|
||||
/// let xs = Intersections::from(Intersection::new(4., &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert_eq!(comps.inside, false);
|
||||
///
|
||||
/// // The hit, when an intersection occurs on the inside.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
||||
/// let shape = Shape::sphere();
|
||||
/// let xs = Intersections::from(Intersection::new(1., &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert_eq!(comps.point, Tuple::point(0., 0., 1.));
|
||||
/// assert_eq!(comps.eyev, Tuple::vector(0., 0., -1.));
|
||||
/// assert_eq!(comps.inside, true);
|
||||
//// // Normal would have been (0, 0, 1), but is inverted when inside.
|
||||
/// assert_eq!(comps.normalv, Tuple::vector(0., 0., -1.));
|
||||
///
|
||||
/// // The hit should offset the point.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let mut shape = Shape::sphere();
|
||||
/// shape .set_transform(Matrix4x4::translation(0.,0.,1.));
|
||||
/// let xs = Intersections::from(Intersection::new(5., &shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r,&xs);
|
||||
/// assert!(comps.over_point.z< -EPSILON/2.);
|
||||
/// assert!(comps.point.z>comps.over_point.z);
|
||||
///
|
||||
/// // Precomputing the reflection vector.
|
||||
/// let shape = Shape::plane();
|
||||
/// let r= Ray::new(point(0.,1.,-1.), 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);
|
||||
/// assert_eq!(comps.reflectv, vector(0.,(2 as Float).sqrt()/2., (2 as Float).sqrt()/2.));
|
||||
///
|
||||
/// // Finding n1 and n2 at various intersections.
|
||||
/// let a = glass_sphere().transform(scaling(2.,2.,2.)).material(MaterialBuilder::default().transparency(1.).refractive_index(1.5).build()?).build()?;
|
||||
/// let b = glass_sphere().transform(translation(0.,0.,-0.25)).material(MaterialBuilder::default().transparency(1.).refractive_index(2.).build()?).build()?;
|
||||
/// let c = glass_sphere().transform(translation(0.,0.,0.25)).material(MaterialBuilder::default().transparency(1.).refractive_index(2.5).build()?).build()?;
|
||||
/// let r = Ray::new(point(0.,0.,-4.), vector(0.,0.,1.));
|
||||
/// let xs = Intersections::new(vec![
|
||||
/// Intersection::new(2., &a),
|
||||
/// Intersection::new(2.75, &b),
|
||||
/// Intersection::new(3.25, &c),
|
||||
/// Intersection::new(4.75, &b),
|
||||
/// Intersection::new(5.25, &c),
|
||||
/// Intersection::new(6., &a),
|
||||
/// ]);
|
||||
/// for (index, n1, n2) in &[
|
||||
/// (0,1.0,1.5),
|
||||
/// (1,1.5,2.0),
|
||||
/// (2,2.0,2.5),
|
||||
/// (3,2.5,2.5),
|
||||
/// (4,2.5,1.5),
|
||||
/// (5,1.5,1.0)]{
|
||||
/// let comps = prepare_computations(&xs[*index], &r, &xs);
|
||||
/// assert_eq!(comps.n1, *n1);
|
||||
/// assert_eq!(comps.n2, *n2);
|
||||
/// }
|
||||
///
|
||||
/// // The under point is offset below the surface.
|
||||
/// let r = Ray::new(point(0.,0.,-5.), vector(0.,0.,1.));
|
||||
/// let shape = glass_sphere().transform(translation(0.,0.,1.)).build()?;
|
||||
/// let xs = Intersections::from(Intersection::new(5.,&shape));
|
||||
/// let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
/// assert!(comps.under_point.z > EPSILON/2.);
|
||||
/// assert!(comps.point.z < comps.under_point.z);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn prepare_computations<'i>(
|
||||
hit: &'i Intersection,
|
||||
r: &Ray,
|
||||
@ -345,83 +205,236 @@ pub fn schlick(comps: &PrecomputedData) -> Float {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
float::consts::SQRT_2,
|
||||
intersections::{prepare_computations, Intersection, Intersections},
|
||||
intersections::{prepare_computations, schlick, Intersection, Intersections},
|
||||
materials::MaterialBuilder,
|
||||
matrices::translation,
|
||||
matrices::{scaling, translation},
|
||||
rays::Ray,
|
||||
shapes::{glass_sphere, plane, sphere},
|
||||
shapes::{glass_sphere, Shape},
|
||||
tuples::{point, vector},
|
||||
world::World,
|
||||
Float, EPSILON,
|
||||
};
|
||||
|
||||
use super::schlick;
|
||||
#[test]
|
||||
fn schlick_under_total_reflection() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
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 reflectance = schlick(&comps);
|
||||
assert_eq!(reflectance, 1.);
|
||||
Ok(())
|
||||
mod hit {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn when_all_intersections_have_positive_t() {
|
||||
let s = Shape::sphere();
|
||||
let i1 = Intersection::new(1., &s);
|
||||
let i2 = Intersection::new(2., &s);
|
||||
let xs = Intersections::new(vec![i2, i1.clone()]);
|
||||
let i = xs.hit();
|
||||
assert_eq!(i, Some(&i1));
|
||||
}
|
||||
#[test]
|
||||
fn when_some_intersections_have_negative_t() {
|
||||
let s = Shape::sphere();
|
||||
let i1 = Intersection::new(-1., &s);
|
||||
let i2 = Intersection::new(1., &s);
|
||||
let xs = Intersections::new(vec![i2.clone(), i1]);
|
||||
let i = xs.hit();
|
||||
assert_eq!(i, Some(&i2));
|
||||
}
|
||||
#[test]
|
||||
fn when_all_intersections_have_negative_t() {
|
||||
let s = Shape::sphere();
|
||||
let i1 = Intersection::new(-2., &s);
|
||||
let i2 = Intersection::new(-1., &s);
|
||||
let xs = Intersections::new(vec![i2, i1]);
|
||||
let i = xs.hit();
|
||||
assert_eq!(i, None);
|
||||
}
|
||||
#[test]
|
||||
fn always_the_lowest_nonnegative_intersection() {
|
||||
let s = Shape::sphere();
|
||||
let i1 = Intersection::new(5., &s);
|
||||
let i2 = Intersection::new(7., &s);
|
||||
let i3 = Intersection::new(-3., &s);
|
||||
let i4 = Intersection::new(2., &s);
|
||||
let xs = Intersections::new(vec![i1, i2, i3, i4.clone()]);
|
||||
let i = xs.hit();
|
||||
assert_eq!(i, Some(&i4));
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn schlick_perpendicular_viewing_angle() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
let r = Ray::new(point(0., 0., 0.), vector(0., 1., 0.));
|
||||
let xs = Intersections::new(vec![
|
||||
Intersection::new(-1., &shape),
|
||||
Intersection::new(1., &shape),
|
||||
]);
|
||||
let comps = prepare_computations(&xs[1], &r, &xs);
|
||||
let reflectance = schlick(&comps);
|
||||
assert!((reflectance - 0.04).abs() < EPSILON);
|
||||
Ok(())
|
||||
|
||||
mod prepare_computations {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn precomputing_the_state_of_intersection() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||
let shape = Shape::sphere();
|
||||
let xs = Intersections::from(Intersection::new(4., &shape));
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
assert_eq!(comps.t, xs[0].t);
|
||||
assert_eq!(comps.object, xs[0].object);
|
||||
assert_eq!(comps.point, point(0., 0., -1.));
|
||||
assert_eq!(comps.eyev, vector(0., 0., -1.));
|
||||
assert_eq!(comps.normalv, vector(0., 0., -1.));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_when_intersection_occurs_outside() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// The hit, when an intersection occurs on the outside.
|
||||
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||
let shape = Shape::sphere();
|
||||
let xs = Intersections::from(Intersection::new(4., &shape));
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
assert_eq!(comps.inside, false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_when_intersection_occurs_inside() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// The hit, when an intersection occurs on the inside.
|
||||
let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.));
|
||||
let shape = Shape::sphere();
|
||||
let xs = Intersections::from(Intersection::new(1., &shape));
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
assert_eq!(comps.point, point(0., 0., 1.));
|
||||
assert_eq!(comps.eyev, vector(0., 0., -1.));
|
||||
assert_eq!(comps.inside, true);
|
||||
// Normal would have been (0, 0, 1), but is inverted when inside.
|
||||
assert_eq!(comps.normalv, vector(0., 0., -1.));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_should_offset_the_point() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// The hit should offset the point.
|
||||
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||
let mut shape = Shape::sphere();
|
||||
shape.set_transform(translation(0., 0., 1.));
|
||||
let xs = Intersections::from(Intersection::new(5., &shape));
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
assert!(comps.over_point.z < -EPSILON / 2.);
|
||||
assert!(comps.point.z > comps.over_point.z);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn precomputing_the_reflection_vector() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Precomputing the reflection vector.
|
||||
let shape = Shape::plane();
|
||||
let r = Ray::new(
|
||||
point(0., 1., -1.),
|
||||
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);
|
||||
assert_eq!(
|
||||
comps.reflectv,
|
||||
vector(0., (2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finding_n1_and_n2() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Finding n1 and n2 at various intersections.
|
||||
let a = glass_sphere()
|
||||
.transform(scaling(2., 2., 2.))
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.transparency(1.)
|
||||
.refractive_index(1.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
let b = glass_sphere()
|
||||
.transform(translation(0., 0., -0.25))
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.transparency(1.)
|
||||
.refractive_index(2.)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
let c = glass_sphere()
|
||||
.transform(translation(0., 0., 0.25))
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.transparency(1.)
|
||||
.refractive_index(2.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
let r = Ray::new(point(0., 0., -4.), vector(0., 0., 1.));
|
||||
let xs = Intersections::new(vec![
|
||||
Intersection::new(2., &a),
|
||||
Intersection::new(2.75, &b),
|
||||
Intersection::new(3.25, &c),
|
||||
Intersection::new(4.75, &b),
|
||||
Intersection::new(5.25, &c),
|
||||
Intersection::new(6., &a),
|
||||
]);
|
||||
for (index, n1, n2) in &[
|
||||
(0, 1.0, 1.5),
|
||||
(1, 1.5, 2.0),
|
||||
(2, 2.0, 2.5),
|
||||
(3, 2.5, 2.5),
|
||||
(4, 2.5, 1.5),
|
||||
(5, 1.5, 1.0),
|
||||
] {
|
||||
let comps = prepare_computations(&xs[*index], &r, &xs);
|
||||
assert_eq!(comps.n1, *n1);
|
||||
assert_eq!(comps.n2, *n2);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn under_point_offset_below_surface() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// The under point is offset below the surface.
|
||||
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||
let shape = glass_sphere().transform(translation(0., 0., 1.)).build()?;
|
||||
let xs = Intersections::from(Intersection::new(5., &shape));
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
assert!(comps.under_point.z > EPSILON / 2.);
|
||||
assert!(comps.point.z < comps.under_point.z);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn schlick_small_angle_n2_greater_n1() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
let r = Ray::new(point(0., 0.99, -2.), vector(0., 0., 1.));
|
||||
let xs = Intersections::new(vec![Intersection::new(1.8589, &shape)]);
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
let reflectance = schlick(&comps);
|
||||
assert!((reflectance - 0.48873).abs() < EPSILON);
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn shade_hit_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 schlick {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn under_total_reflection() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
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 reflectance = schlick(&comps);
|
||||
assert_eq!(reflectance, 1.);
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn perpendicular_viewing_angle() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
let r = Ray::new(point(0., 0., 0.), vector(0., 1., 0.));
|
||||
let xs = Intersections::new(vec![
|
||||
Intersection::new(-1., &shape),
|
||||
Intersection::new(1., &shape),
|
||||
]);
|
||||
let comps = prepare_computations(&xs[1], &r, &xs);
|
||||
let reflectance = schlick(&comps);
|
||||
assert!((reflectance - 0.04).abs() < EPSILON);
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn small_angle_n2_greater_n1() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shape = glass_sphere().build()?;
|
||||
let r = Ray::new(point(0., 0.99, -2.), vector(0., 0., 1.));
|
||||
let xs = Intersections::new(vec![Intersection::new(1.8589, &shape)]);
|
||||
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||
let reflectance = schlick(&comps);
|
||||
assert!((reflectance - 0.48873).abs() < EPSILON);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user