311 lines
10 KiB
Rust
311 lines
10 KiB
Rust
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,
|
|
)])
|
|
}
|
|
}
|