use std::ops::Index; use crate::{ rays::Ray, shapes::Shape, tuples::{dot, Tuple}, Float, EPSILON, }; #[derive(Debug, Clone, PartialEq)] pub struct Intersection<'i> { pub t: Float, pub object: &'i Shape, } impl<'i> Intersection<'i> { /// Create new `Intersection` at the given `t` that hits the given `object`. /// /// # Examples /// ``` /// use rtchallenge::{intersections::Intersection, shapes::Shape}; /// /// // An intersection ecapsulates t and object. /// let s = Shape::sphere(); /// let i = Intersection::new(3.5, &s); /// assert_eq!(i.t, 3.5); /// assert_eq!(i.object, &s); /// ``` pub fn new(t: Float, object: &Shape) -> Intersection { Intersection { t, object } } } /// Aggregates `Intersection`s. /// /// # Examples /// ``` /// use rtchallenge::{ /// intersections::{Intersection, Intersections}, /// rays::Ray, /// shapes::{intersect, Shape}, /// tuples::Tuple, /// }; /// /// let s = Shape::sphere(); /// 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>); impl<'i> Intersections<'i> { pub fn new(xs: Vec>) -> 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, /// 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) .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; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'i> Index for Intersections<'i> { type Output = Intersection<'i>; fn index(&self, idx: usize) -> &Self::Output { &self.0[idx] } } #[derive(Debug)] pub struct PrecomputedData<'i> { pub t: Float, pub object: &'i Shape, pub point: Tuple, pub over_point: Tuple, pub eyev: Tuple, pub normalv: Tuple, pub inside: bool, } /// Precomputes data common to all intersections. /// /// # Examples /// ``` /// use rtchallenge::{ /// intersections::{prepare_computations, Intersection, Intersections}, /// rays::Ray, /// matrices::Matrix4x4, /// shapes::{intersect, Shape}, /// tuples::Tuple, /// EPSILON /// }; /// /// // 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 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.)); /// /// // 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 i = Intersection::new(4., &shape); /// let comps = prepare_computations(&i, &r); /// 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 i = Intersection::new(1., &shape); /// let comps = prepare_computations(&i, &r); /// 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 i = Intersection::new(5., &shape); /// let comps = prepare_computations(&i, &r); /// assert!(comps.over_point.z< -EPSILON/2.); /// assert!(comps.point.z>comps.over_point.z); /// ``` pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData<'i> { let point = r.position(i.t); let normalv = i.object.normal_at(point); let eyev = -r.direction; let (inside, normalv) = if dot(normalv, eyev) < 0. { (true, -normalv) } else { (false, normalv) }; let over_point = point + normalv * EPSILON; PrecomputedData { t: i.t, object: i.object, point, over_point, normalv, inside, eyev, } }