shapes: add AABB boxes with a cube shape.
This commit is contained in:
parent
7a80179f41
commit
de6cd0da4d
@ -46,7 +46,7 @@ pub mod prelude {
|
|||||||
materials::{Material, MaterialBuilder},
|
materials::{Material, MaterialBuilder},
|
||||||
matrices::{identity, rotation_x, rotation_y, rotation_z, scaling, shearing, translation},
|
matrices::{identity, rotation_x, rotation_y, rotation_z, scaling, shearing, translation},
|
||||||
patterns::{checkers_pattern, gradient_pattern, ring_pattern, stripe_pattern},
|
patterns::{checkers_pattern, gradient_pattern, ring_pattern, stripe_pattern},
|
||||||
shapes::{plane, sphere, test_shape},
|
shapes::{cube, plane, sphere, test_shape},
|
||||||
transformations::view_transform,
|
transformations::view_transform,
|
||||||
tuples::{point, vector, Color},
|
tuples::{point, vector, Color},
|
||||||
world::{World, WorldBuilder},
|
world::{World, WorldBuilder},
|
||||||
|
|||||||
@ -23,6 +23,8 @@ pub enum Geometry {
|
|||||||
Sphere,
|
Sphere,
|
||||||
/// Flat surface that extends infinitely in the XZ axes.
|
/// Flat surface that extends infinitely in the XZ axes.
|
||||||
Plane,
|
Plane,
|
||||||
|
/// AABB cube at origin from -1,1 in each direction.
|
||||||
|
Cube,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Geometry {
|
impl Default for Geometry {
|
||||||
@ -38,6 +40,7 @@ impl PartialEq for Geometry {
|
|||||||
(TestShape(l), TestShape(r)) => *l.lock().unwrap() == *r.lock().unwrap(),
|
(TestShape(l), TestShape(r)) => *l.lock().unwrap() == *r.lock().unwrap(),
|
||||||
(Sphere, Sphere) => true,
|
(Sphere, Sphere) => true,
|
||||||
(Plane, Plane) => true,
|
(Plane, Plane) => true,
|
||||||
|
(Cube, Cube) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,6 +73,11 @@ pub fn test_shape() -> ShapeBuilder {
|
|||||||
ShapeBuilder::test_shape()
|
ShapeBuilder::test_shape()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Short hand for creating a ShapeBuilder with a cube geometry.
|
||||||
|
pub fn cube() -> ShapeBuilder {
|
||||||
|
ShapeBuilder::cube()
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper for producing a sphere with a glassy material.
|
/// Helper for producing a sphere with a glassy material.
|
||||||
pub fn glass_sphere() -> ShapeBuilder {
|
pub fn glass_sphere() -> ShapeBuilder {
|
||||||
ShapeBuilder::sphere().material(
|
ShapeBuilder::sphere().material(
|
||||||
@ -95,6 +103,11 @@ impl ShapeBuilder {
|
|||||||
TestData::default(),
|
TestData::default(),
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
/// Short hand for creating a ShapeBuilder with a cube geometry.
|
||||||
|
pub fn cube() -> ShapeBuilder {
|
||||||
|
ShapeBuilder::default().geometry(Geometry::Cube)
|
||||||
|
}
|
||||||
|
|
||||||
fn default_inverse_transform(&self) -> Result<Matrix4x4, String> {
|
fn default_inverse_transform(&self) -> Result<Matrix4x4, String> {
|
||||||
Ok(self.transform.unwrap_or(Matrix4x4::identity()).inverse())
|
Ok(self.transform.unwrap_or(Matrix4x4::identity()).inverse())
|
||||||
}
|
}
|
||||||
@ -121,7 +134,6 @@ impl Shape {
|
|||||||
geometry: Geometry::TestShape(Arc::new(Mutex::new(TestData::default()))),
|
geometry: Geometry::TestShape(Arc::new(Mutex::new(TestData::default()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// # Examples
|
|
||||||
pub fn sphere() -> Shape {
|
pub fn sphere() -> Shape {
|
||||||
Shape {
|
Shape {
|
||||||
transform: Matrix4x4::identity(),
|
transform: Matrix4x4::identity(),
|
||||||
@ -138,6 +150,14 @@ impl Shape {
|
|||||||
geometry: Geometry::Plane,
|
geometry: Geometry::Plane,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn cube() -> Shape {
|
||||||
|
Shape {
|
||||||
|
transform: Matrix4x4::identity(),
|
||||||
|
inverse_transform: Matrix4x4::identity(),
|
||||||
|
material: Material::default(),
|
||||||
|
geometry: Geometry::Cube,
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Find the normal at the point on the sphere.
|
/// Find the normal at the point on the sphere.
|
||||||
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;
|
||||||
@ -145,6 +165,7 @@ impl Shape {
|
|||||||
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.),
|
Geometry::Plane => Tuple::vector(0., 1., 0.),
|
||||||
Geometry::TestShape(_) => object_point,
|
Geometry::TestShape(_) => object_point,
|
||||||
|
Geometry::Cube => cube::local_normal_at(object_point),
|
||||||
};
|
};
|
||||||
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.;
|
||||||
@ -175,6 +196,7 @@ pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
|||||||
Geometry::Sphere => sphere::intersect(shape, &local_ray),
|
Geometry::Sphere => sphere::intersect(shape, &local_ray),
|
||||||
Geometry::Plane => plane::intersect(shape, &local_ray),
|
Geometry::Plane => plane::intersect(shape, &local_ray),
|
||||||
Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray),
|
Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray),
|
||||||
|
Geometry::Cube => cube::intersect(shape, &local_ray),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,6 +257,129 @@ mod plane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod cube {
|
||||||
|
use crate::{
|
||||||
|
intersections::{Intersection, Intersections},
|
||||||
|
rays::Ray,
|
||||||
|
shapes::Shape,
|
||||||
|
tuples::{vector, Tuple},
|
||||||
|
Float, EPSILON,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn check_axis(origin: Float, direction: Float) -> (Float, Float) {
|
||||||
|
let tmin_numerator = -1. - origin;
|
||||||
|
let tmax_numerator = 1. - origin;
|
||||||
|
|
||||||
|
let (tmin, tmax) = if direction.abs() >= EPSILON {
|
||||||
|
(tmin_numerator / direction, tmax_numerator / direction)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
tmin_numerator * Float::INFINITY,
|
||||||
|
tmax_numerator * Float::INFINITY,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if tmin > tmax {
|
||||||
|
return (tmax, tmin);
|
||||||
|
}
|
||||||
|
(tmin, tmax)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
||||||
|
let (xtmin, xtmax) = check_axis(ray.origin.x, ray.direction.x);
|
||||||
|
let (ytmin, ytmax) = check_axis(ray.origin.y, ray.direction.y);
|
||||||
|
let (ztmin, ztmax) = check_axis(ray.origin.z, ray.direction.z);
|
||||||
|
|
||||||
|
let tmin = xtmin.max(ytmin).max(ztmin);
|
||||||
|
let tmax = xtmax.min(ytmax).min(ztmax);
|
||||||
|
|
||||||
|
if tmin > tmax {
|
||||||
|
return Intersections::default();
|
||||||
|
}
|
||||||
|
Intersections::new(vec![
|
||||||
|
Intersection::new(tmin, &shape),
|
||||||
|
Intersection::new(tmax, &shape),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
pub fn local_normal_at(point: Tuple) -> Tuple {
|
||||||
|
let x = point.x.abs();
|
||||||
|
let y = point.y.abs();
|
||||||
|
let z = point.z.abs();
|
||||||
|
let maxc = x.max(y).max(z);
|
||||||
|
if maxc == x {
|
||||||
|
return vector(point.x, 0., 0.);
|
||||||
|
}
|
||||||
|
if maxc == y {
|
||||||
|
return vector(0., point.y, 0.);
|
||||||
|
}
|
||||||
|
vector(0., 0., point.z)
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
rays::Ray,
|
||||||
|
shapes::Shape,
|
||||||
|
tuples::{point, vector},
|
||||||
|
EPSILON,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{intersect, local_normal_at};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ray_intersects_cube() {
|
||||||
|
let c = Shape::cube();
|
||||||
|
for (name, o, d, t1, t2) in [
|
||||||
|
("+x", point(5., 0.5, 0.), vector(-1., 0., 0.), 4., 6.),
|
||||||
|
("-x", point(-5., 0.5, 0.), vector(1., 0., 0.), 4., 6.),
|
||||||
|
("+y", point(0.5, 5., 0.), vector(0., -1., 0.), 4., 6.),
|
||||||
|
("-y", point(0.5, -5., 0.), vector(0., 1., 0.), 4., 6.),
|
||||||
|
("+z", point(0.5, 0., 5.), vector(0., 0., -1.), 4., 6.),
|
||||||
|
("-z", point(0.5, 0., -5.), vector(0., 0., 1.), 4., 6.),
|
||||||
|
("inside", point(0., 0.5, 0.), vector(0., 0., 1.), -1., 1.),
|
||||||
|
] {
|
||||||
|
let r = Ray::new(o, d);
|
||||||
|
let xs = intersect(&c, &r);
|
||||||
|
assert_eq!(xs.len(), 2, "{}", name);
|
||||||
|
assert!((xs[0].t - t1).abs() < EPSILON, "{} t1 {}", name, xs[0].t);
|
||||||
|
assert!((xs[1].t - t2).abs() < EPSILON, "{} t2 {}", name, xs[1].t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ray_misses_cube() {
|
||||||
|
let c = Shape::cube();
|
||||||
|
for (o, d) in [
|
||||||
|
(point(-2., 0., 0.), vector(0.2673, 0.5345, 0.8018)),
|
||||||
|
(point(0., -2., 0.), vector(0.8018, 0.2673, 0.5345)),
|
||||||
|
(point(0., 0., -2.), vector(0.5345, 0.8018, 0.2673)),
|
||||||
|
(point(2., 0., 2.), vector(0., 0., -1.)),
|
||||||
|
(point(0., 2., 2.), vector(0., -1., 0.)),
|
||||||
|
(point(2., 2., -5.), vector(-1., 0., 0.)),
|
||||||
|
] {
|
||||||
|
let r = Ray::new(o, d);
|
||||||
|
let xs = intersect(&c, &r);
|
||||||
|
assert_eq!(xs.len(), 0, "({:?}, {:?})", o, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn normal_cube_surface() {
|
||||||
|
for (p, n) in [
|
||||||
|
(point(1., 0.8, -0.8), vector(1., 0., 0.)),
|
||||||
|
(point(-1., -0.2, 0.9), vector(-1., 0., 0.)),
|
||||||
|
(point(-0.4, 1., -0.1), vector(0., 1., 0.)),
|
||||||
|
(point(0.3, -1., -0.7), vector(0., -1., 0.)),
|
||||||
|
(point(-0.6, 0.3, 1.), vector(0., 0., 1.)),
|
||||||
|
(point(0.4, 0.4, -1.), vector(0., 0., -1.)),
|
||||||
|
(point(1., 1., 1.), vector(1., 0., 0.)),
|
||||||
|
(point(-1., -1., -1.), vector(-1., 0., 0.)),
|
||||||
|
] {
|
||||||
|
let normal = local_normal_at(p);
|
||||||
|
assert_eq!(n, normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
mod shape_builder {
|
mod shape_builder {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user