This commit is contained in:
@@ -22,7 +22,7 @@ use crate::{
|
||||
Float, BLACK,
|
||||
};
|
||||
|
||||
const MAX_DEPTH_RECURSION: usize = 5;
|
||||
const MAX_DEPTH_RECURSION: usize = 10;
|
||||
|
||||
#[derive(Copy, Clone, StructOpt, Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
|
||||
@@ -50,5 +50,6 @@ pub mod prelude {
|
||||
transformations::view_transform,
|
||||
tuples::{point, vector, Color},
|
||||
world::{World, WorldBuilder},
|
||||
Float,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use derive_builder::Builder;
|
||||
use crate::{
|
||||
intersections::Intersections,
|
||||
materials::{Material, MaterialBuilder},
|
||||
matrices::Matrix4x4,
|
||||
matrices::{identity, Matrix4x4},
|
||||
rays::Ray,
|
||||
tuples::Tuple,
|
||||
};
|
||||
@@ -25,6 +25,8 @@ pub enum Geometry {
|
||||
Plane,
|
||||
/// AABB cube at origin from -1,1 in each direction.
|
||||
Cube,
|
||||
/// Takes shape from children held within.
|
||||
Group(Vec<Shape>),
|
||||
}
|
||||
|
||||
impl Default for Geometry {
|
||||
@@ -41,6 +43,7 @@ impl PartialEq for Geometry {
|
||||
(Sphere, Sphere) => true,
|
||||
(Plane, Plane) => true,
|
||||
(Cube, Cube) => true,
|
||||
(Group(v1), Group(v2)) => v1 == v2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -50,10 +53,9 @@ impl PartialEq for Geometry {
|
||||
/// 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(Builder, Debug, Clone, PartialEq)]
|
||||
#[builder(default, pattern = "owned")]
|
||||
#[builder(default, pattern = "owned", build_fn(skip))]
|
||||
pub struct Shape {
|
||||
transform: Matrix4x4,
|
||||
#[builder(private, default = "self.default_inverse_transform()?")]
|
||||
inverse_transform: Matrix4x4,
|
||||
pub material: Material,
|
||||
geometry: Geometry,
|
||||
@@ -78,6 +80,11 @@ pub fn cube() -> ShapeBuilder {
|
||||
ShapeBuilder::cube()
|
||||
}
|
||||
|
||||
/// Short hand for creating a ShapeBuilder with a group geometry.
|
||||
pub fn group() -> ShapeBuilder {
|
||||
ShapeBuilder::group()
|
||||
}
|
||||
|
||||
/// Helper for producing a sphere with a glassy material.
|
||||
pub fn glass_sphere() -> ShapeBuilder {
|
||||
ShapeBuilder::sphere().material(
|
||||
@@ -107,9 +114,38 @@ impl ShapeBuilder {
|
||||
pub fn cube() -> ShapeBuilder {
|
||||
ShapeBuilder::default().geometry(Geometry::Cube)
|
||||
}
|
||||
/// Short hand for creating a ShapeBuilder with a group geometry.
|
||||
pub fn group() -> ShapeBuilder {
|
||||
ShapeBuilder::default().geometry(Geometry::Group(Vec::new()))
|
||||
}
|
||||
|
||||
fn default_inverse_transform(&self) -> Result<Matrix4x4, String> {
|
||||
Ok(self.transform.unwrap_or(Matrix4x4::identity()).inverse())
|
||||
/// Add child shapes, only valid for Group::Geometry.
|
||||
pub fn add_child(mut self, child: Shape) -> ShapeBuilder {
|
||||
if let Some(Geometry::Group(ref mut children)) = &mut self.geometry {
|
||||
children.push(child);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&self) -> Result<Shape, ShapeBuilderError> {
|
||||
let mut s = Shape::default();
|
||||
|
||||
if let Some(transform) = &self.transform {
|
||||
s.set_transform(transform.clone());
|
||||
}
|
||||
if let Some(material) = &self.material {
|
||||
s.material = material.clone();
|
||||
};
|
||||
if let Some(geometry) = &self.geometry {
|
||||
s.geometry = geometry.clone();
|
||||
};
|
||||
let transform = s.transform().clone();
|
||||
if let Geometry::Group(ref mut children) = s.geometry {
|
||||
children
|
||||
.into_iter()
|
||||
.for_each(|c| c.set_transform(transform * c.transform()));
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +194,14 @@ impl Shape {
|
||||
geometry: Geometry::Cube,
|
||||
}
|
||||
}
|
||||
pub fn group() -> Shape {
|
||||
Shape {
|
||||
transform: Matrix4x4::identity(),
|
||||
inverse_transform: Matrix4x4::identity(),
|
||||
material: Material::default(),
|
||||
geometry: Geometry::Group(Vec::new()),
|
||||
}
|
||||
}
|
||||
/// Find the normal at the point on the sphere.
|
||||
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
|
||||
let object_point = self.inverse_transform * world_point;
|
||||
@@ -166,6 +210,7 @@ impl Shape {
|
||||
Geometry::Plane => Tuple::vector(0., 1., 0.),
|
||||
Geometry::TestShape(_) => object_point,
|
||||
Geometry::Cube => cube::local_normal_at(object_point),
|
||||
Geometry::Group(_) => todo!("normal_at"),
|
||||
};
|
||||
let mut world_normal = self.inverse_transform.transpose() * object_normal;
|
||||
world_normal.w = 0.;
|
||||
@@ -197,6 +242,7 @@ pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
||||
Geometry::Plane => plane::intersect(shape, &local_ray),
|
||||
Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray),
|
||||
Geometry::Cube => cube::intersect(shape, &local_ray),
|
||||
Geometry::Group(_) => group::intersect(shape, &ray),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,6 +426,83 @@ mod cube {
|
||||
}
|
||||
}
|
||||
|
||||
mod group {
|
||||
use crate::{intersections::Intersections, rays::Ray, shapes::Shape};
|
||||
|
||||
use super::Geometry;
|
||||
|
||||
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
||||
if let Geometry::Group(children) = &shape.geometry {
|
||||
let mut intersections: Vec<_> = children
|
||||
.iter()
|
||||
.map(|c| super::intersect(c, ray))
|
||||
.flatten()
|
||||
.collect();
|
||||
intersections.sort_by(|a, b| {
|
||||
a.t.partial_cmp(&b.t)
|
||||
.expect("an intersection has a t value that is NaN")
|
||||
});
|
||||
return Intersections::new(intersections);
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
matrices::{scaling, translation},
|
||||
rays::Ray,
|
||||
shapes::{intersect, Shape, ShapeBuilder},
|
||||
tuples::{point, vector},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn intersecting_empty_group() {
|
||||
let g = Shape::group();
|
||||
let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.));
|
||||
let xs = intersect(&g, &r);
|
||||
assert_eq!(xs.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersecting_nonempty_group() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let s1 = Shape::sphere();
|
||||
let s2 = ShapeBuilder::sphere()
|
||||
.transform(translation(0., 0., -3.))
|
||||
.build()?;
|
||||
let s3 = ShapeBuilder::sphere()
|
||||
.transform(translation(5., 0., 0.))
|
||||
.build()?;
|
||||
let g = ShapeBuilder::group()
|
||||
.add_child(s1.clone())
|
||||
.add_child(s2.clone())
|
||||
.add_child(s3.clone())
|
||||
.build()?;
|
||||
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||
let xs = intersect(&g, &r);
|
||||
assert_eq!(xs.len(), 4);
|
||||
assert_eq!(xs[0].object, &s2);
|
||||
assert_eq!(xs[1].object, &s2);
|
||||
assert_eq!(xs[2].object, &s1);
|
||||
assert_eq!(xs[3].object, &s1);
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn intersecting_transformed_group() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let g = ShapeBuilder::group()
|
||||
.transform(scaling(2., 2., 2.))
|
||||
.add_child(
|
||||
ShapeBuilder::sphere()
|
||||
.transform(translation(5., 0., 0.))
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
let r = Ray::new(point(10., 0., -10.), vector(0., 0., 1.));
|
||||
let xs = intersect(&g, &r);
|
||||
assert_eq!(xs.len(), 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod shape_builder {
|
||||
|
||||
Reference in New Issue
Block a user