Matrix transformations

This commit is contained in:
Savanni D'Gerinel 2024-06-10 09:34:59 -04:00
parent 971206d325
commit a2aa132886
3 changed files with 262 additions and 10 deletions

View File

@ -1,5 +1,5 @@
mod ppm; mod ppm;
pub mod transforms;
pub mod types; pub mod types;
pub use ppm::PPM; pub use ppm::PPM;

View File

@ -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.));
}
}

View File

@ -1,4 +1,4 @@
use crate::types::{eq_f64, Tuple}; use crate::types::{eq_f64, Tuple, Point, Vector};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Matrix { pub struct Matrix {
@ -44,7 +44,11 @@ impl Matrix {
m 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 mut m = Matrix::new(self.size);
let determinant = self.determinant(); let determinant = self.determinant();
for row in 0..self.size { 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 { fn submatrix(&self, row: usize, column: usize) -> Self {
let mut m = Self::new(self.size - 1); let mut m = Self::new(self.size - 1);
@ -209,6 +209,20 @@ impl std::ops::Mul<Tuple> for Matrix {
} }
} }
impl std::ops::Mul<Point> for Matrix {
type Output = Point;
fn mul(self, rside: Point) -> Point {
Point::from(self * *rside)
}
}
impl std::ops::Mul<Vector> for Matrix {
type Output = Vector;
fn mul(self, rside: Vector) -> Vector {
Vector::from(self * *rside)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -401,7 +415,7 @@ mod tests {
]); ]);
assert_eq!(m.determinant(), -2120.); assert_eq!(m.determinant(), -2120.);
assert!(m.invertible()); assert!(m.is_invertible());
let m = Matrix::from([ let m = Matrix::from([
[-4., 2., -2., -3.], [-4., 2., -2., -3.],
@ -410,7 +424,7 @@ mod tests {
[0., 0., 0., 0.], [0., 0., 0., 0.],
]); ]);
assert_eq!(m.determinant(), 0.); assert_eq!(m.determinant(), 0.);
assert!(!m.invertible()); assert!(!m.is_invertible());
let m = Matrix::from([ let m = Matrix::from([
[-5., 2., 6., -8.], [-5., 2., 6., -8.],
@ -431,6 +445,52 @@ mod tests {
assert_eq!(m.cofactor(3, 2), 105.); assert_eq!(m.cofactor(3, 2), 105.);
assert!(eq_f64(expected.cell(2, 3), 105. / 532.)); 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);
} }
} }