use crate::{ intersections::Intersections, materials::Material, matrices::Matrix4x4, rays::Ray, tuples::Tuple, }; #[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 /// 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(Debug, PartialEq, Clone)] pub struct Shape { transform: Matrix4x4, inverse_transform: Matrix4x4, pub material: Material, geometry: Geometry, } impl Shape { /// # Examples /// ``` /// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape}; /// /// // A sphere's default transform is the identity matrix. /// let s = Shape::sphere(); /// assert_eq!(s.transform(), Matrix4x4::identity()); /// /// // It can be changed by directly setting the transform member. /// let mut s = Shape::sphere(); /// let t = Matrix4x4::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); /// ``` 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, } } /// Find the normal at the point on the sphere. /// /// # Examples /// ``` /// use rtchallenge::{ /// float::consts::PI, materials::Material, matrices::Matrix4x4, shapes::Shape, tuples::Tuple, /// Float, /// }; /// /// // 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.)); /// /// // 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.)); /// /// // 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.)); /// /// // 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., /// ) /// ); /// // 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()); /// /// // 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)); /// /// // 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)); /// /// // 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.; world_normal.normalize() } #[cfg(feature = "disable-inverse-cache")] pub fn normal_at(&self, world_point: Tuple) -> Tuple { 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.; world_normal.normalize() } pub fn transform(&self) -> Matrix4x4 { self.transform } pub fn set_transform(&mut self, t: Matrix4x4) { self.transform = t; self.inverse_transform = t.inverse(); } } /// Intersect a ray with a sphere. /// /// # Examples /// ``` /// use rtchallenge::{ /// intersections::{Intersection, Intersections}, /// matrices::Matrix4x4, /// rays::Ray, /// shapes::{intersect, Shape}, /// tuples::Tuple, /// }; /// /// // 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)]) /// ); /// /// // 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()); /// /// // 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)]) /// ); /// /// // 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)]) /// ); /// /// // 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); /// /// // 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); /// /// // 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 => sphere::intersect(shape, ray), Geometry::Plane => plane::intersect(shape, ray), } } 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 ray = ray.transform(shape.inverse_transform); 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, )]) } }