diff --git a/TODO.md b/TODO.md index 8148138..45823dd 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,12 @@ TODO -[ ] Transformations -[ ] Area lights -[ ] Textures -[ ] Film -[ ] Thin lens camera -[ ] Samplers -[ ] Spectrum +- [ ] Transformations + - [x] Matrix + - [x] Spheres + - [ ] Triangles +- [ ] Area lights +- [ ] Textures +- [ ] Film +- [ ] Thin lens camera +- [ ] Samplers +- [ ] Spectrum diff --git a/src/intersection.rs b/src/intersection.rs index fb81914..7c688b4 100644 --- a/src/intersection.rs +++ b/src/intersection.rs @@ -3,6 +3,7 @@ use crate::{ material::Material, }; +#[derive(Debug, PartialEq)] pub struct ShapeIntersection { pub location: Point, // TODO: Get the normal lazily when needed diff --git a/src/ray.rs b/src/ray.rs index 94f5660..18b1f90 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -18,12 +18,17 @@ impl Ray { max_distance: f64::INFINITY, } } - pub fn update_max_distance(&mut self, distance: f64) -> Option { + + pub fn at(&self, distance: f64) -> Point { + self.origin + self.direction * distance + } + + pub fn update_max_distance(&mut self, distance: f64) -> bool { if distance > EPSILON && distance < self.max_distance { self.max_distance = distance; - Some(self.origin + self.direction * distance) + true } else { - None + false } } } diff --git a/src/shape.rs b/src/shape.rs index 2f98808..d566361 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -1,15 +1,17 @@ use crate::{ bounds::Bounds, constants::EPSILON, - geometry::{point::Point, traits::DotProduct, vector::Vector}, + geometry::{normal::Normal, point::Point, traits::DotProduct, vector::Vector}, intersection::ShapeIntersection, ray::Ray, + transformation::{Transformable, Transformation}, }; #[derive(Debug, PartialEq)] pub enum Shape { Sphere { - origin: Point, + object_to_world: Transformation, + world_to_object: Transformation, radius: f64, radius_squared: f64, inv_radius: f64, @@ -27,10 +29,11 @@ pub enum Shape { impl Shape { pub fn new_sphere(origin: Point, radius: f64) -> Shape { Shape::Sphere { - origin, radius, radius_squared: radius * radius, inv_radius: 1.0 / radius, + object_to_world: Transformation::translate(origin.x(), origin.y(), origin.z()), + world_to_object: Transformation::translate(-origin.x(), -origin.y(), -origin.z()), } } pub fn new_triangle(v0: Point, v1: Point, v2: Point) -> Shape { @@ -76,15 +79,18 @@ impl Shape { pub fn intersect(&self, ray: &mut Ray) -> Option { match self { Shape::Sphere { - origin, + object_to_world, + world_to_object, radius_squared, inv_radius, .. } => { - let oc = ray.origin - *origin; - let a = ray.direction.magnitude_squared(); - let b = 2.0 * oc.dot(&ray.direction); - let c = oc.magnitude_squared() - radius_squared; + let mut obj_ray = world_to_object.transform(ray); + + let oc = Vector(obj_ray.origin.x(), obj_ray.origin.y(), obj_ray.origin.z()); + let a = obj_ray.direction.magnitude_squared(); + let b = 2.0 * oc.dot(&obj_ray.direction); + let c = oc.magnitude_squared() - *radius_squared; let discriminant = b * b - 4.0 * a * c; if discriminant < 0.0 { @@ -94,19 +100,23 @@ impl Shape { let discriminant_sqrt = discriminant.sqrt(); let inv_2_a = 1.0 / (2.0 * a); let mut distance = (-b - discriminant_sqrt) * inv_2_a; - if let Some(location) = ray.update_max_distance(distance) { - return Some(ShapeIntersection { + if obj_ray.update_max_distance(distance) { + let location = obj_ray.at(distance); + ray.update_max_distance(distance); + return Some(object_to_world.transform(&ShapeIntersection { location, - normal: ((location - *origin) * *inv_radius).into(), - }); + normal: Normal(location.x(), location.y(), location.z()) * *inv_radius, + })); } distance = (-b + discriminant_sqrt) * inv_2_a; - if let Some(location) = ray.update_max_distance(distance) { - return Some(ShapeIntersection { + if obj_ray.update_max_distance(distance) { + let location = obj_ray.at(distance); + ray.update_max_distance(distance); + return Some(object_to_world.transform(&ShapeIntersection { location, - normal: ((location - *origin) * *inv_radius).into(), - }); + normal: Normal(location.x(), location.y(), location.z()) * *inv_radius, + })); } None @@ -141,7 +151,8 @@ impl Shape { } let distance = T.cross(e1).dot(e2) * inv_denominator; - if let Some(location) = ray.update_max_distance(distance) { + if ray.update_max_distance(distance) { + let location = ray.at(distance); Some(ShapeIntersection { location, normal: (*n0 + *n01 * u + *n02 * v).normalized().into(), @@ -154,18 +165,14 @@ impl Shape { } pub fn bounds(&self) -> Bounds { match self { - Shape::Sphere { origin, radius, .. } => Bounds::new( - Point( - origin.x() - radius, - origin.y() - radius, - origin.z() - radius, - ), - Point( - origin.x() + radius, - origin.y() + radius, - origin.z() + radius, - ), - ), + Shape::Sphere { + object_to_world, + radius, + .. + } => object_to_world.transform(&Bounds::new( + Point(-radius, -radius, -radius), + Point(*radius, *radius, *radius), + )), Shape::Triangle { v0, e1, e2, .. } => { let v1 = *v0 + *e1; let v2 = *v0 + *e2; diff --git a/src/transformation.rs b/src/transformation.rs index 1029f1d..f4d541f 100644 --- a/src/transformation.rs +++ b/src/transformation.rs @@ -5,10 +5,11 @@ use crate::{ bounds::Bounds, constants::EPSILON, geometry::{normal::Normal, point::Point, vector::Vector}, + intersection::ShapeIntersection, ray::Ray, }; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Matrix { pub m: [[f64; 4]; 4], } @@ -243,27 +244,35 @@ impl Display for Matrix { } } +#[derive(Debug, PartialEq, Clone)] pub struct Transformation { pub matrix: Matrix, pub inverse: Matrix, } impl Transformation { - pub fn translate(delta: &Vector) -> Self { + pub fn inverse(&self) -> Self { + Transformation { + matrix: self.inverse.clone(), + inverse: self.matrix.clone(), + } + } + + pub fn translate(dx: f64, dy: f64, dz: f64) -> Self { Transformation { matrix: Matrix { m: [ - [1.0, 0.0, 0.0, delta.x()], - [0.0, 1.0, 0.0, delta.y()], - [0.0, 0.0, 1.0, delta.z()], + [1.0, 0.0, 0.0, dx], + [0.0, 1.0, 0.0, dy], + [0.0, 0.0, 1.0, dz], [0.0, 0.0, 0.0, 1.0], ], }, inverse: Matrix { m: [ - [1.0, 0.0, 0.0, -delta.x()], - [0.0, 1.0, 0.0, -delta.y()], - [0.0, 0.0, 1.0, -delta.z()], + [1.0, 0.0, 0.0, -dx], + [0.0, 1.0, 0.0, -dy], + [0.0, 0.0, 1.0, -dz], [0.0, 0.0, 0.0, 1.0], ], }, @@ -340,6 +349,17 @@ impl Transformation { } } +impl Mul for &Transformation { + type Output = Transformation; + + fn mul(self, rhs: Self) -> Transformation { + Transformation { + matrix: &self.matrix * &rhs.matrix, + inverse: &rhs.inverse * &self.inverse, + } + } +} + pub trait Transformable { fn transform(&self, t: &T) -> T; } @@ -406,3 +426,12 @@ impl Transformable for Transformation { .sum() } } + +impl Transformable for Transformation { + fn transform(&self, intersection: &ShapeIntersection) -> ShapeIntersection { + ShapeIntersection { + location: self.transform(&intersection.location), + normal: self.transform(&intersection.normal), + } + } +} diff --git a/tests/test_shape.rs b/tests/test_shape.rs index 8bf72ff..bc75bb6 100644 --- a/tests/test_shape.rs +++ b/tests/test_shape.rs @@ -1,7 +1,48 @@ mod sphere { - use craytracer::{bounds::Bounds, geometry::point::Point, shape::Shape}; + use craytracer::{ + bounds::Bounds, + geometry::{normal::Normal, point::Point, vector::Vector}, + intersection::ShapeIntersection, + ray::Ray, + shape::Shape, + }; use pretty_assertions::assert_eq; + #[test] + fn intersect() { + let s = Shape::new_sphere(Point(1.0, 2.0, 3.0), 2.0); + + let ray = &mut Ray::new(Point(1.0, 2.0, 0.0), Vector::Z); + let intersection = s.intersect(ray); + assert_eq!( + intersection, + Some(ShapeIntersection { + location: Point(1.0, 2.0, 1.0), + normal: Normal(0.0, 0.0, -1.0), + }) + ); + + let ray = &mut Ray::new(Point(-3.0, 2.0, 3.0), Vector::X); + let intersection = s.intersect(ray); + assert_eq!( + intersection, + Some(ShapeIntersection { + location: Point(-1.0, 2.0, 3.0), + normal: Normal(-1.0, 0.0, 0.0), + }) + ); + + let ray = &mut Ray::new(Point(1.0, -2.0, 3.0), Vector::Y); + let intersection = s.intersect(ray); + assert_eq!( + intersection, + Some(ShapeIntersection { + location: Point(1.0, 0.0, 3.0), + normal: Normal(0.0, -1.0, 0.0), + }) + ); + } + #[test] fn bounds() { assert_eq!( diff --git a/tests/test_transformation.rs b/tests/test_transformation.rs index 20d3a0c..fc35bda 100644 --- a/tests/test_transformation.rs +++ b/tests/test_transformation.rs @@ -5,7 +5,7 @@ pub mod matrix { use pretty_assertions::assert_eq; #[test] - pub fn test_mul() { + pub fn mul() { let m1 = Matrix::new([ // Dürer's magic square [16, 3, 2, 13], @@ -35,7 +35,7 @@ pub mod matrix { } #[test] - pub fn test_inverse() { + pub fn inverse() { let m = Matrix::new([ // [1, 3, 5, 4], @@ -79,8 +79,8 @@ pub mod transformation { use pretty_assertions::assert_eq; #[test] - pub fn test_translation() { - let t = Transformation::translate(&Vector(5.0, -3.0, 2.0)); + pub fn translation() { + let t = Transformation::translate(5.0, -3.0, 2.0); assert_eq!(t.transform(&Point(-3.0, 4.0, 5.0)), Point(2.0, 1.0, 7.0)); assert_eq!(t.transform(&Vector(-3.0, 4.0, 5.0)), Vector(-3.0, 4.0, 5.0)); @@ -96,7 +96,7 @@ pub mod transformation { } #[test] - pub fn test_scale() { + pub fn scale() { let t = Transformation::scale(2.0, -3.0, 0.5); assert_eq!(t.transform(&Point(-3.0, 4.0, 5.0)), Point(-6.0, -12.0, 2.5)); @@ -119,7 +119,7 @@ pub mod transformation { } #[test] - pub fn test_rotate_x() { + pub fn rotate_x() { let t = Transformation::rotate_x(90.0); assert_abs_diff_eq!(t.transform(&Point(2.0, 1.0, 3.0)), Point(2.0, -3.0, 1.0),); @@ -132,7 +132,7 @@ pub mod transformation { } #[test] - pub fn test_rotate_y() { + pub fn rotate_y() { let t = Transformation::rotate_y(90.0); assert_abs_diff_eq!(t.transform(&Point(2.0, 1.0, 3.0)), Point(3.0, 1.0, -2.0),); @@ -145,7 +145,7 @@ pub mod transformation { } #[test] - pub fn test_rotate_z() { + pub fn rotate_z() { let t = Transformation::rotate_z(90.0); assert_abs_diff_eq!(t.transform(&Point(2.0, 1.0, 3.0)), Point(-1.0, 2.0, 3.0),);