diff --git a/rtchallenge/src/shapes.rs b/rtchallenge/src/shapes.rs index 9116666..bf31b20 100644 --- a/rtchallenge/src/shapes.rs +++ b/rtchallenge/src/shapes.rs @@ -4,12 +4,15 @@ use crate::{ matrices::Matrix4x4, rays::Ray, tuples::{dot, Tuple}, + EPSILON, }; #[derive(Debug, PartialEq, Clone)] enum Geometry { /// 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, } /// Shape represents visible objects. A signal instance of Shape can generically represent one of @@ -55,6 +58,14 @@ impl Shape { geometry: Geometry::Sphere, } } + pub fn plane() -> Shape { + Shape { + transform: Matrix4x4::identity(), + inverse_transform: Matrix4x4::identity(), + material: Material::default(), + geometry: Geometry::Plane, + } + } /// Find the normal at the point on the sphere. /// /// # Examples @@ -118,12 +129,28 @@ impl Shape { /// -(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.) + /// ); /// ``` #[cfg(not(feature = "disable-inverse-cache"))] 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.), }; let mut world_normal = self.inverse_transform.transpose() * object_normal; world_normal.w = 0.; @@ -134,6 +161,7 @@ impl Shape { let object_point = self.transform.inverse() * world_point; let object_normal = match self.geometry { Geometry::Sphere => object_point - Tuple::point(0., 0., 0.), + Geometry::Plane => Tuple::vector(0., 1., 0.), }; let mut world_normal = self.transform.inverse().transpose() * object_normal; world_normal.w = 0.; @@ -210,14 +238,40 @@ impl Shape { /// 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> { match shape.geometry { - Geometry::Sphere => intersect_rtc(shape, ray), + Geometry::Sphere => intersect_sphere(shape, ray), + Geometry::Plane => intersect_plane(shape, ray), } } -fn intersect_rtc<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> { +fn intersect_sphere<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> { let ray = ray.transform(shape.inverse_transform); let sphere_to_ray = ray.origin - Tuple::point(0., 0., 0.); let a = dot(ray.direction, ray.direction); @@ -232,18 +286,12 @@ fn intersect_rtc<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> { Intersection::new((-b + discriminant.sqrt()) / (2. * a), &shape), ]) } -fn intersect_rtiow<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> { - let ray = ray.transform(shape.inverse_transform); - let oc = ray.origin - Tuple::point(0., 0., 0.); - let a = dot(ray.direction, ray.direction); - let b = dot(oc, ray.direction); - let c = dot(oc, oc) - 1.; - let discriminant = b * b - a * c; - if discriminant < 0. { +fn intersect_plane<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> { + if (ray.direction.y).abs() < EPSILON { return Intersections::default(); } - Intersections::new(vec![ - Intersection::new((-b - discriminant.sqrt()) / a, &shape), - Intersection::new((-b + discriminant.sqrt()) / a, &shape), - ]) + Intersections::new(vec![Intersection::new( + -ray.origin.y / ray.direction.y, + &shape, + )]) }