172 lines
5.0 KiB
Rust
172 lines
5.0 KiB
Rust
use std::ops::Index;
|
|
|
|
use crate::{rays::Ray, spheres::Sphere, tuples::Tuple};
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct Intersection<'i> {
|
|
pub t: f32,
|
|
pub object: &'i Sphere,
|
|
}
|
|
|
|
impl<'i> Intersection<'i> {
|
|
/// Create new `Intersection` at the given `t` that hits the given `object`.
|
|
///
|
|
/// # Examples
|
|
/// ```
|
|
/// use rtchallenge::{intersections::Intersection, spheres::Sphere};
|
|
///
|
|
/// // An intersection ecapsulates t and object.
|
|
/// let s = Sphere::default();
|
|
/// let i = Intersection::new(3.5, &s);
|
|
/// assert_eq!(i.t, 3.5);
|
|
/// assert_eq!(i.object, &s);
|
|
/// ```
|
|
pub fn new(t: f32, object: &Sphere) -> Intersection {
|
|
Intersection { t, object }
|
|
}
|
|
}
|
|
|
|
/// Aggregates `Intersection`s.
|
|
///
|
|
/// # Examples
|
|
/// ```
|
|
/// use rtchallenge::{
|
|
/// intersections::{Intersection, Intersections},
|
|
/// rays::Ray,
|
|
/// spheres::{intersect, Sphere},
|
|
/// tuples::Tuple,
|
|
/// };
|
|
///
|
|
/// let s = Sphere::default();
|
|
/// let i1 = Intersection::new(1., &s);
|
|
/// let i2 = Intersection::new(2., &s);
|
|
/// let xs = Intersections::new(vec![i1, i2]);
|
|
/// assert_eq!(xs.len(), 2);
|
|
/// assert_eq!(xs[0].t, 1.);
|
|
/// assert_eq!(xs[1].t, 2.);
|
|
///
|
|
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
/// let xs = intersect(&s, &r);
|
|
/// assert_eq!(xs.len(), 2);
|
|
/// assert_eq!(xs[0].object, &s);
|
|
/// assert_eq!(xs[1].object, &s);
|
|
/// ```
|
|
#[derive(Debug, Default, PartialEq)]
|
|
pub struct Intersections<'i>(Vec<Intersection<'i>>);
|
|
|
|
impl<'i> Intersections<'i> {
|
|
pub fn new(xs: Vec<Intersection<'i>>) -> Intersections {
|
|
Intersections(xs)
|
|
}
|
|
pub fn len(&self) -> usize {
|
|
self.0.len()
|
|
}
|
|
/// Finds nearest hit for this collection of intersections.
|
|
///
|
|
/// # Examples
|
|
/// ```
|
|
/// use rtchallenge::{
|
|
/// intersections::{Intersection, Intersections},
|
|
/// rays::Ray,
|
|
/// spheres::{intersect, Sphere},
|
|
/// tuples::Tuple,
|
|
/// };
|
|
///
|
|
/// // The hit, when all intersections have positive t.
|
|
/// let s = Sphere::default();
|
|
/// 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 = Sphere::default();
|
|
/// 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 = Sphere::default();
|
|
/// 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 = Sphere::default();
|
|
/// 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)
|
|
.expect("an intersection has a t value that is NaN")
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'i> IntoIterator for Intersections<'i> {
|
|
type Item = Intersection<'i>;
|
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.0.into_iter()
|
|
}
|
|
}
|
|
|
|
impl<'i> Index<usize> for Intersections<'i> {
|
|
type Output = Intersection<'i>;
|
|
fn index(&self, idx: usize) -> &Self::Output {
|
|
&self.0[idx]
|
|
}
|
|
}
|
|
|
|
pub struct PrecomputedData<'i> {
|
|
pub t: f32,
|
|
pub object: &'i Sphere,
|
|
pub point: Tuple,
|
|
pub eyev: Tuple,
|
|
pub normalv: Tuple,
|
|
}
|
|
|
|
/// Precomputes data common to all intersections.
|
|
///
|
|
/// # Examples
|
|
/// ```
|
|
/// use rtchallenge::{
|
|
/// intersections::{prepare_computations, Intersection, Intersections},
|
|
/// rays::Ray,
|
|
/// spheres::{intersect, Sphere},
|
|
/// tuples::Tuple,
|
|
/// };
|
|
///
|
|
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
/// let shape = Sphere::default();
|
|
/// let i = Intersection::new(4., &shape);
|
|
/// let comps = prepare_computations(&i, &r);
|
|
/// assert_eq!(comps.t, i.t);
|
|
/// assert_eq!(comps.object, i.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.));
|
|
/// ```
|
|
pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData<'i> {
|
|
let point = r.position(i.t);
|
|
PrecomputedData {
|
|
t: i.t,
|
|
object: i.object,
|
|
point,
|
|
eyev: -r.direction,
|
|
normalv: i.object.normal_at(point),
|
|
}
|
|
}
|