shapes: implement plane geometry.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Bill Thiede 2021-07-21 12:57:16 -07:00
parent 2f85697b88
commit 0e8a0e4163

View File

@ -4,12 +4,15 @@ use crate::{
matrices::Matrix4x4, matrices::Matrix4x4,
rays::Ray, rays::Ray,
tuples::{dot, Tuple}, tuples::{dot, Tuple},
EPSILON,
}; };
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
enum Geometry { enum Geometry {
/// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0. /// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0.
Sphere, 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 /// Shape represents visible objects. A signal instance of Shape can generically represent one of
@ -55,6 +58,14 @@ impl Shape {
geometry: Geometry::Sphere, 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. /// Find the normal at the point on the sphere.
/// ///
/// # Examples /// # Examples
@ -118,12 +129,28 @@ impl Shape {
/// -(2. as Float).sqrt() / 2., /// -(2. as Float).sqrt() / 2.,
/// )); /// ));
/// assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254)); /// 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"))] #[cfg(not(feature = "disable-inverse-cache"))]
pub fn normal_at(&self, world_point: Tuple) -> Tuple { pub fn normal_at(&self, world_point: Tuple) -> Tuple {
let object_point = self.inverse_transform * world_point; let object_point = self.inverse_transform * world_point;
let object_normal = match self.geometry { let object_normal = match self.geometry {
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.), 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; let mut world_normal = self.inverse_transform.transpose() * object_normal;
world_normal.w = 0.; world_normal.w = 0.;
@ -134,6 +161,7 @@ impl Shape {
let object_point = self.transform.inverse() * world_point; let object_point = self.transform.inverse() * world_point;
let object_normal = match self.geometry { let object_normal = match self.geometry {
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.), 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; let mut world_normal = self.transform.inverse().transpose() * object_normal;
world_normal.w = 0.; world_normal.w = 0.;
@ -210,14 +238,40 @@ impl Shape {
/// s.set_transform(Matrix4x4::translation(5., 0., 0.)); /// s.set_transform(Matrix4x4::translation(5., 0., 0.));
/// let xs = intersect(&s, &r); /// let xs = intersect(&s, &r);
/// assert_eq!(xs.len(), 0); /// 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> { pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
match shape.geometry { 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 ray = ray.transform(shape.inverse_transform);
let sphere_to_ray = ray.origin - Tuple::point(0., 0., 0.); let sphere_to_ray = ray.origin - Tuple::point(0., 0., 0.);
let a = dot(ray.direction, ray.direction); 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), Intersection::new((-b + discriminant.sqrt()) / (2. * a), &shape),
]) ])
} }
fn intersect_rtiow<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> { fn intersect_plane<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
let ray = ray.transform(shape.inverse_transform); if (ray.direction.y).abs() < EPSILON {
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. {
return Intersections::default(); return Intersections::default();
} }
Intersections::new(vec![ Intersections::new(vec![Intersection::new(
Intersection::new((-b - discriminant.sqrt()) / a, &shape), -ray.origin.y / ray.direction.y,
Intersection::new((-b + discriminant.sqrt()) / a, &shape), &shape,
]) )])
} }