From a2aa13288691961e5ebced4941997ffa2213a8da Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Mon, 10 Jun 2024 09:34:59 -0400 Subject: [PATCH] Matrix transformations --- ray-tracer/src/lib.rs | 2 +- ray-tracer/src/transforms.rs | 192 +++++++++++++++++++++++++++++++++ ray-tracer/src/types/matrix.rs | 78 ++++++++++++-- 3 files changed, 262 insertions(+), 10 deletions(-) create mode 100644 ray-tracer/src/transforms.rs diff --git a/ray-tracer/src/lib.rs b/ray-tracer/src/lib.rs index b6604d1..90caafd 100644 --- a/ray-tracer/src/lib.rs +++ b/ray-tracer/src/lib.rs @@ -1,5 +1,5 @@ mod ppm; - +pub mod transforms; pub mod types; pub use ppm::PPM; diff --git a/ray-tracer/src/transforms.rs b/ray-tracer/src/transforms.rs new file mode 100644 index 0000000..762d772 --- /dev/null +++ b/ray-tracer/src/transforms.rs @@ -0,0 +1,192 @@ +use crate::types::Matrix; + +pub fn translation(x: f64, y: f64, z: f64) -> Matrix { + Matrix::from([ + [1., 0., 0., x], + [0., 1., 0., y], + [0., 0., 1., z], + [0., 0., 0., 1.], + ]) +} + +pub fn scaling(x: f64, y: f64, z: f64) -> Matrix { + Matrix::from([ + [x, 0., 0., 0.], + [0., y, 0., 0.], + [0., 0., z, 0.], + [0., 0., 0., 1.], + ]) +} + +pub fn rotation_x(r: f64) -> Matrix { + Matrix::from([ + [1., 0., 0., 0.], + [0., r.cos(), -r.sin(), 0.], + [0., r.sin(), r.cos(), 0.], + [0., 0., 0., 1.], + ]) +} + +pub fn rotation_y(r: f64) -> Matrix { + Matrix::from([ + [r.cos(), 0., r.sin(), 0.], + [0., 1., 0., 0.], + [-r.sin(), 0., r.cos(), 0.], + [0., 0., 0., 1.], + ]) +} + +pub fn rotation_z(r: f64) -> Matrix { + Matrix::from([ + [r.cos(), -r.sin(), 0., 0.], + [r.sin(), r.cos(), 0., 0.], + [0., 0., 1., 0.], + [0., 0., 0., 1.], + ]) +} + +pub fn shearing( + x_by_y: f64, + x_by_z: f64, + y_by_x: f64, + y_by_z: f64, + z_by_x: f64, + z_by_y: f64, +) -> Matrix { + Matrix::from([ + [1., x_by_y, x_by_z, 0.], + [y_by_x, 1., y_by_z, 0.], + [z_by_x, z_by_y, 1., 0.], + [0., 0., 0., 1.], + ]) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::{Point, Vector}; + use std::f64::consts::PI; + + #[test] + fn multiply_by_translation_matrix() { + let tr = translation(5., -3., 2.); + let p = Point::new(-3., 4., 5.); + assert_eq!(tr.clone() * p, Point::new(2., 1., 7.)); + + let inv = tr.inverse(); + assert_eq!(inv * p, Point::new(-8., 7., 3.)); + } + + #[test] + fn translation_does_not_affect_vectors() { + let tr = translation(5., -3., 2.); + let v = Vector::new(-3., 4., 5.); + assert_eq!(tr.clone() * v, v); + } + + #[test] + fn scaling_matrix_applied_to_point() { + let tr = scaling(2., 3., 4.); + let p = Point::new(-4., 6., 8.); + let v = Vector::new(-4., 6., 8.); + + assert_eq!(tr.clone() * p, Point::new(-8., 18., 32.)); + assert_eq!(tr * v, Vector::new(-8., 18., 32.)); + } + + #[test] + fn reflection_is_scaling_by_negative_value() { + let tr = scaling(-1., 1., 1.); + let p = Point::new(2., 3., 4.); + assert_eq!(tr * p, Point::new(-2., 3., 4.)); + } + + #[test] + fn rotate_point_around_x() { + let p = Point::new(0., 1., 0.); + let half_quarter = rotation_x(PI / 4.); + let full_quarter = rotation_x(PI / 2.); + assert_eq!( + half_quarter * p, + Point::new(0., 2_f64.sqrt() / 2., 2_f64.sqrt() / 2.) + ); + assert_eq!(full_quarter * p, Point::new(0., 0., 1.)); + } + + #[test] + fn inverse_rotates_opposite_direction() { + let p = Point::new(0., 1., 0.); + let half_quarter = rotation_x(PI / 4.); + let inv = half_quarter.inverse(); + assert_eq!( + inv * p, + Point::new(0., 2_f64.sqrt() / 2., -2_f64.sqrt() / 2.) + ); + } + + #[test] + fn rotate_around_y() { + let p = Point::new(0., 0., 1.); + let half_quarter = rotation_y(PI / 4.); + let full_quarter = rotation_y(PI / 2.); + assert_eq!( + half_quarter * p, + Point::new(2_f64.sqrt() / 2., 0., 2_f64.sqrt() / 2.) + ); + assert_eq!(full_quarter * p, Point::new(1., 0., 0.)); + } + + #[test] + fn rotate_around_z() { + let p = Point::new(0., 1., 0.); + let half_quarter = rotation_z(PI / 4.); + let full_quarter = rotation_z(PI / 2.); + assert_eq!( + half_quarter * p, + Point::new(-2_f64.sqrt() / 2., 2_f64.sqrt() / 2., 0.) + ); + assert_eq!(full_quarter * p, Point::new(-1., 0., 0.)); + } + + #[test] + fn shear_moves_x_proportionally_to_y() { + let transform = shearing(1., 0., 0., 0., 0., 0.); + let p = Point::new(2., 3., 4.); + assert_eq!(transform * p, Point::new(5., 3., 4.)); + } + + #[test] + fn shear_moves_x_proportionally_to_z() { + let transform = shearing(0., 1., 0., 0., 0., 0.); + let p = Point::new(2., 3., 4.); + assert_eq!(transform * p, Point::new(6., 3., 4.)); + } + + #[test] + fn shear_moves_y_proportionally_to_x() { + let transform = shearing(0., 0., 1., 0., 0., 0.); + let p = Point::new(2., 3., 4.); + assert_eq!(transform * p, Point::new(2., 5., 4.)); + } + + #[test] + fn shear_moves_y_proportionally_to_z() { + let transform = shearing(0., 0., 0., 1., 0., 0.); + let p = Point::new(2., 3., 4.); + assert_eq!(transform * p, Point::new(2., 7., 4.)); + } + + #[test] + fn shear_moves_z_proportionally_to_x() { + let transform = shearing(0., 0., 0., 0., 1., 0.); + let p = Point::new(2., 3., 4.); + assert_eq!(transform * p, Point::new(2., 3., 6.)); + } + + #[test] + fn shear_moves_z_proportionally_to_y() { + let transform = shearing(0., 0., 0., 0., 0., 1.); + let p = Point::new(2., 3., 4.); + assert_eq!(transform * p, Point::new(2., 3., 7.)); + } +} diff --git a/ray-tracer/src/types/matrix.rs b/ray-tracer/src/types/matrix.rs index 40257cd..ed008fb 100644 --- a/ray-tracer/src/types/matrix.rs +++ b/ray-tracer/src/types/matrix.rs @@ -1,4 +1,4 @@ -use crate::types::{eq_f64, Tuple}; +use crate::types::{eq_f64, Tuple, Point, Vector}; #[derive(Clone, Debug)] pub struct Matrix { @@ -44,7 +44,11 @@ impl Matrix { m } - pub fn invert(&self) -> Self { + pub fn is_invertible(&self) -> bool { + !eq_f64(self.determinant(), 0.) + } + + pub fn inverse(&self) -> Self { let mut m = Matrix::new(self.size); let determinant = self.determinant(); for row in 0..self.size { @@ -83,10 +87,6 @@ impl Matrix { } } - fn invertible(&self) -> bool { - !eq_f64(self.determinant(), 0.) - } - fn submatrix(&self, row: usize, column: usize) -> Self { let mut m = Self::new(self.size - 1); @@ -209,6 +209,20 @@ impl std::ops::Mul for Matrix { } } +impl std::ops::Mul for Matrix { + type Output = Point; + fn mul(self, rside: Point) -> Point { + Point::from(self * *rside) + } +} + +impl std::ops::Mul for Matrix { + type Output = Vector; + fn mul(self, rside: Vector) -> Vector { + Vector::from(self * *rside) + } +} + #[cfg(test)] mod tests { use super::*; @@ -401,7 +415,7 @@ mod tests { ]); assert_eq!(m.determinant(), -2120.); - assert!(m.invertible()); + assert!(m.is_invertible()); let m = Matrix::from([ [-4., 2., -2., -3.], @@ -410,7 +424,7 @@ mod tests { [0., 0., 0., 0.], ]); assert_eq!(m.determinant(), 0.); - assert!(!m.invertible()); + assert!(!m.is_invertible()); let m = Matrix::from([ [-5., 2., 6., -8.], @@ -431,6 +445,52 @@ mod tests { assert_eq!(m.cofactor(3, 2), 105.); assert!(eq_f64(expected.cell(2, 3), 105. / 532.)); - assert_eq!(m.invert(), expected); + assert_eq!(m.inverse(), expected); + + let m = Matrix::from([ + [8., -5., 9., 2.], + [7., 5., 6., 1.], + [-6., 0., 9., 6.], + [-3., 0., -9., -4.], + ]); + let expected = Matrix::from([ + [-0.15385, -0.15385, -0.28205, -0.53846], + [-0.07692, 0.12308, 0.02564, 0.03077], + [0.35897, 0.35897, 0.43590, 0.92308], + [-0.69231, -0.69231, -0.76923, -1.92308], + ]); + assert_eq!(m.inverse(), expected); + + let m = Matrix::from([ + [9., 3., 0., 9.], + [-5., -2., -6., -3.], + [-4., 9., 6., 4.], + [-7., 6., 6., 2.], + ]); + let expected = Matrix::from([ + [-0.04074, -0.07778, 0.14444, -0.22222], + [-0.07778, 0.03333, 0.36667, -0.33333], + [-0.02901, -0.14630, -0.10926, 0.12963], + [0.17778, 0.06667, -0.26667, 0.33333], + ]); + assert_eq!(m.inverse(), expected); + } + + #[test] + fn multiply_product_by_inverse() { + let a = Matrix::from([ + [3., -9., 7., 3.], + [3., -8., 2., -9.], + [-4., 4., 4., 1.], + [-6., 5., -1., 1.], + ]); + let b = Matrix::from([ + [8., 2., 2., 2.], + [3., -1., 7., 0.], + [7., 0., 5., 4.], + [6., -2., 0., 5.], + ]); + let c = a.clone() * b.clone(); + assert_eq!(c * b.inverse(), a); } }