use std::ops::{Mul, MulAssign}; use crate::{nearly_equal, Vector2}; /// Represents a 3x3 column-major matrix and provides common methods for matrix math. #[derive(Debug, Copy, Clone)] pub struct Matrix3x3 { pub m: [f32; 9], } impl Matrix3x3 { pub const M11: usize = 0; pub const M12: usize = 3; pub const M13: usize = 6; pub const M21: usize = 1; pub const M22: usize = 4; pub const M23: usize = 7; pub const M31: usize = 2; pub const M32: usize = 5; pub const M33: usize = 8; pub const IDENTITY: Matrix3x3 = Matrix3x3 { m: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], }; /// Returns a new identity matrix. #[inline] pub fn identity() -> Matrix3x3 { Matrix3x3::IDENTITY } /// Creates a new matrix with the specified elements. #[rustfmt::skip] #[inline] pub fn new( m11: f32, m12: f32, m13: f32, m21: f32, m22: f32, m23: f32, m31: f32, m32: f32, m33: f32, ) -> Matrix3x3 { Matrix3x3 { m: [ m11, m21, m31, m12, m22, m32, m13, m23, m33 ], } } /// Creates a new rotation matrix from a set of euler angles. /// /// # Arguments /// /// * `x`: the x angle (in radians) /// * `y`: the y angle (in radians) /// * `z`: the z angle (in radians) pub fn from_euler_angles(x: f32, y: f32, z: f32) -> Matrix3x3 { let rotate_z = Matrix3x3::new_rotation_z(z); let rotate_y = Matrix3x3::new_rotation_y(y); let rotate_x = Matrix3x3::new_rotation_x(x); // "right-to-left" column-major matrix concatenation rotate_z * rotate_y * rotate_x } /// Creates a new rotation matrix for rotation around the x axis. /// /// # Arguments /// /// * `radians`: angle to rotate the x axis around (in radians) #[rustfmt::skip] #[inline] pub fn new_rotation_x(radians: f32) -> Matrix3x3 { let (s, c) = radians.sin_cos(); Matrix3x3::new( 1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c ) } /// Creates a new rotation matrix for rotation around the y axis. /// /// # Arguments /// /// * `radians`: angle to rotate the y axis around (in radians) #[rustfmt::skip] #[inline] pub fn new_rotation_y(radians: f32) -> Matrix3x3 { let (s, c) = radians.sin_cos(); Matrix3x3::new( c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c ) } /// Creates a new rotation matrix for rotation around the z axis. /// /// # Arguments /// /// * `radians`: angle to rotate the z axis around (in radians) #[rustfmt::skip] #[inline] pub fn new_rotation_z(radians: f32) -> Matrix3x3 { let (s, c) = radians.sin_cos(); Matrix3x3::new( c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0 ) } /// Creates a translation matrix. For use with 2D coordinates only. /// /// # Arguments /// /// * `x`: the amount to translate on the x axis /// * `y`: the amount to translate on the y axis #[rustfmt::skip] #[inline] pub fn new_2d_translation(x: f32, y: f32) -> Matrix3x3 { Matrix3x3::new( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, x, y, 1.0 ) } /// Creates a scaling matrix from scaling factors for each axis. For use with 2D coordinates /// only. /// /// # Arguments /// /// * `x`: the scale factor for the x axis /// * `y`: the scale factor for the y axis #[rustfmt::skip] #[inline] pub fn new_2d_scaling(x: f32, y: f32) -> Matrix3x3 { Matrix3x3::new( x, 0.0, 0.0, 0.0, y, 0.0, 0.0, 0.0, 1.0 ) } /// Creates a new rotation matrix. For use with 2D coordinates only. /// /// # Arguments /// /// * `radians`: angle to rotate by (in radians) #[inline(always)] pub fn new_2d_rotation(radians: f32) -> Matrix3x3 { Matrix3x3::new_rotation_z(radians) } /// Calculates the determinant of this matrix. #[rustfmt::skip] #[inline] pub fn determinant(&self) -> f32 { self.m[Matrix3x3::M11] * self.m[Matrix3x3::M22] * self.m[Matrix3x3::M33] + self.m[Matrix3x3::M12] * self.m[Matrix3x3::M23] * self.m[Matrix3x3::M31] + self.m[Matrix3x3::M13] * self.m[Matrix3x3::M21] * self.m[Matrix3x3::M32] - self.m[Matrix3x3::M11] * self.m[Matrix3x3::M23] * self.m[Matrix3x3::M32] - self.m[Matrix3x3::M12] * self.m[Matrix3x3::M21] * self.m[Matrix3x3::M33] - self.m[Matrix3x3::M13] * self.m[Matrix3x3::M22] * self.m[Matrix3x3::M31] } /// Calculates the inverse of this matrix. #[rustfmt::skip] pub fn invert(&self) -> Matrix3x3 { let d = self.determinant(); if nearly_equal(d, 0.0, 0.000001) { Matrix3x3::IDENTITY } else { let d = 1.0 / d; Matrix3x3 { m: [ d * (self.m[Matrix3x3::M22] * self.m[Matrix3x3::M33] - self.m[Matrix3x3::M32] * self.m[Matrix3x3::M23]), d * (self.m[Matrix3x3::M31] * self.m[Matrix3x3::M23] - self.m[Matrix3x3::M21] * self.m[Matrix3x3::M33]), d * (self.m[Matrix3x3::M21] * self.m[Matrix3x3::M32] - self.m[Matrix3x3::M31] * self.m[Matrix3x3::M22]), d * (self.m[Matrix3x3::M32] * self.m[Matrix3x3::M13] - self.m[Matrix3x3::M12] * self.m[Matrix3x3::M33]), d * (self.m[Matrix3x3::M11] * self.m[Matrix3x3::M33] - self.m[Matrix3x3::M31] * self.m[Matrix3x3::M13]), d * (self.m[Matrix3x3::M31] * self.m[Matrix3x3::M12] - self.m[Matrix3x3::M11] * self.m[Matrix3x3::M32]), d * (self.m[Matrix3x3::M12] * self.m[Matrix3x3::M23] - self.m[Matrix3x3::M22] * self.m[Matrix3x3::M13]), d * (self.m[Matrix3x3::M21] * self.m[Matrix3x3::M13] - self.m[Matrix3x3::M11] * self.m[Matrix3x3::M23]), d * (self.m[Matrix3x3::M11] * self.m[Matrix3x3::M22] - self.m[Matrix3x3::M21] * self.m[Matrix3x3::M12]), ] } } } /// Calculates the transpose of this matrix. #[inline] pub fn transpose(&self) -> Matrix3x3 { Matrix3x3::new( self.m[Matrix3x3::M11], self.m[Matrix3x3::M21], self.m[Matrix3x3::M31], self.m[Matrix3x3::M12], self.m[Matrix3x3::M22], self.m[Matrix3x3::M32], self.m[Matrix3x3::M13], self.m[Matrix3x3::M23], self.m[Matrix3x3::M33], ) } /// Sets all of the elements of this matrix. #[inline] pub fn set( &mut self, m11: f32, m12: f32, m13: f32, m21: f32, m22: f32, m23: f32, m31: f32, m32: f32, m33: f32, ) { self.m[Matrix3x3::M11] = m11; self.m[Matrix3x3::M12] = m12; self.m[Matrix3x3::M13] = m13; self.m[Matrix3x3::M21] = m21; self.m[Matrix3x3::M22] = m22; self.m[Matrix3x3::M23] = m23; self.m[Matrix3x3::M31] = m31; self.m[Matrix3x3::M32] = m32; self.m[Matrix3x3::M33] = m33; } } impl Mul for Matrix3x3 { type Output = Self; #[rustfmt::skip] #[inline] fn mul(self, rhs: Self) -> Self::Output { Matrix3x3::new( self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M33], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M33], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M33] ) } } impl MulAssign for Matrix3x3 { #[rustfmt::skip] #[inline] fn mul_assign(&mut self, rhs: Self) { self.set( self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M11] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M12] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M13] * rhs.m[Matrix3x3::M33], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M21] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M22] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M23] * rhs.m[Matrix3x3::M33], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M11] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M21] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M31], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M12] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M22] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M32], self.m[Matrix3x3::M31] * rhs.m[Matrix3x3::M13] + self.m[Matrix3x3::M32] * rhs.m[Matrix3x3::M23] + self.m[Matrix3x3::M33] * rhs.m[Matrix3x3::M33] ) } } impl Mul for Matrix3x3 { type Output = Vector2; #[rustfmt::skip] #[inline] fn mul(self, rhs: Vector2) -> Self::Output { Vector2 { x: rhs.x * self.m[Matrix3x3::M11] + rhs.y * self.m[Matrix3x3::M12] + self.m[Matrix3x3::M13] + self.m[Matrix3x3::M31], y: rhs.x * self.m[Matrix3x3::M21] + rhs.y * self.m[Matrix3x3::M22] + self.m[Matrix3x3::M23] + self.m[Matrix3x3::M32] } } } #[cfg(test)] pub mod tests { use crate::math::{RADIANS_180, RADIANS_90}; use super::*; #[test] pub fn test_new() { let m = Matrix3x3::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0); assert_eq!(1.0, m.m[Matrix3x3::M11]); assert_eq!(2.0, m.m[Matrix3x3::M12]); assert_eq!(3.0, m.m[Matrix3x3::M13]); assert_eq!(4.0, m.m[Matrix3x3::M21]); assert_eq!(5.0, m.m[Matrix3x3::M22]); assert_eq!(6.0, m.m[Matrix3x3::M23]); assert_eq!(7.0, m.m[Matrix3x3::M31]); assert_eq!(8.0, m.m[Matrix3x3::M32]); assert_eq!(9.0, m.m[Matrix3x3::M33]); } #[test] pub fn test_set() { let mut m = Matrix3x3 { m: [0.0; 9] }; m.set(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0); assert_eq!(1.0, m.m[Matrix3x3::M11]); assert_eq!(2.0, m.m[Matrix3x3::M12]); assert_eq!(3.0, m.m[Matrix3x3::M13]); assert_eq!(4.0, m.m[Matrix3x3::M21]); assert_eq!(5.0, m.m[Matrix3x3::M22]); assert_eq!(6.0, m.m[Matrix3x3::M23]); assert_eq!(7.0, m.m[Matrix3x3::M31]); assert_eq!(8.0, m.m[Matrix3x3::M32]); assert_eq!(9.0, m.m[Matrix3x3::M33]); } #[test] pub fn test_identity() { let m = Matrix3x3::identity(); assert_eq!(1.0, m.m[Matrix3x3::M11]); assert_eq!(0.0, m.m[Matrix3x3::M12]); assert_eq!(0.0, m.m[Matrix3x3::M13]); assert_eq!(0.0, m.m[Matrix3x3::M21]); assert_eq!(1.0, m.m[Matrix3x3::M22]); assert_eq!(0.0, m.m[Matrix3x3::M23]); assert_eq!(0.0, m.m[Matrix3x3::M31]); assert_eq!(0.0, m.m[Matrix3x3::M32]); assert_eq!(1.0, m.m[Matrix3x3::M33]); } #[rustfmt::skip] #[test] pub fn test_transpose() { let m = Matrix3x3::new( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 ); let t = m.transpose(); assert_eq!(1.0, t.m[Matrix3x3::M11]); assert_eq!(4.0, t.m[Matrix3x3::M12]); assert_eq!(7.0, t.m[Matrix3x3::M13]); assert_eq!(2.0, t.m[Matrix3x3::M21]); assert_eq!(5.0, t.m[Matrix3x3::M22]); assert_eq!(8.0, t.m[Matrix3x3::M23]); assert_eq!(3.0, t.m[Matrix3x3::M31]); assert_eq!(6.0, t.m[Matrix3x3::M32]); assert_eq!(9.0, t.m[Matrix3x3::M33]); } #[test] pub fn test_mul() { let a = Matrix3x3::new(12.0, 8.0, 4.0, 3.0, 17.0, 14.0, 9.0, 8.0, 10.0); let b = Matrix3x3::new(5.0, 19.0, 3.0, 6.0, 15.0, 9.0, 7.0, 8.0, 16.0); let c = a * b; assert!(nearly_equal(136.0, c.m[Matrix3x3::M11], 0.001)); assert!(nearly_equal(380.0, c.m[Matrix3x3::M12], 0.001)); assert!(nearly_equal(172.0, c.m[Matrix3x3::M13], 0.001)); assert!(nearly_equal(215.0, c.m[Matrix3x3::M21], 0.001)); assert!(nearly_equal(424.0, c.m[Matrix3x3::M22], 0.001)); assert!(nearly_equal(386.0, c.m[Matrix3x3::M23], 0.001)); assert!(nearly_equal(163.0, c.m[Matrix3x3::M31], 0.001)); assert!(nearly_equal(371.0, c.m[Matrix3x3::M32], 0.001)); assert!(nearly_equal(259.0, c.m[Matrix3x3::M33], 0.001)); let mut a = Matrix3x3::new(12.0, 8.0, 4.0, 3.0, 17.0, 14.0, 9.0, 8.0, 10.0); let b = Matrix3x3::new(5.0, 19.0, 3.0, 6.0, 15.0, 9.0, 7.0, 8.0, 16.0); a *= b; assert!(nearly_equal(136.0, a.m[Matrix3x3::M11], 0.001)); assert!(nearly_equal(380.0, a.m[Matrix3x3::M12], 0.001)); assert!(nearly_equal(172.0, a.m[Matrix3x3::M13], 0.001)); assert!(nearly_equal(215.0, a.m[Matrix3x3::M21], 0.001)); assert!(nearly_equal(424.0, a.m[Matrix3x3::M22], 0.001)); assert!(nearly_equal(386.0, a.m[Matrix3x3::M23], 0.001)); assert!(nearly_equal(163.0, a.m[Matrix3x3::M31], 0.001)); assert!(nearly_equal(371.0, a.m[Matrix3x3::M32], 0.001)); assert!(nearly_equal(259.0, a.m[Matrix3x3::M33], 0.001)); } #[test] pub fn test_2d_translation() { let v = Vector2::new(10.2, 5.7); let m = Matrix3x3::new_2d_translation(2.0, 3.0); let t = m * v; assert!(nearly_equal(12.2, t.x, 0.001)); assert!(nearly_equal(8.7, t.y, 0.001)); } #[test] pub fn test_2d_scaling() { let v = Vector2::new(10.2, 5.7); let m = Matrix3x3::new_2d_scaling(3.0, 4.0); let t = m * v; assert!(nearly_equal(30.6, t.x, 0.001)); assert!(nearly_equal(22.8, t.y, 0.001)); } #[test] pub fn test_2d_rotation() { let v = Vector2::new(0.0, 5.0); let m = Matrix3x3::new_2d_rotation(RADIANS_90); let t = m * v; assert!(nearly_equal(-5.0, t.x, 0.001)); assert!(nearly_equal(0.0, t.y, 0.001)); } #[test] pub fn test_2d_combined_transform() { let a = Matrix3x3::new_2d_translation(-2.0, 0.0); let b = Matrix3x3::new_2d_rotation(RADIANS_180); let m = a * b; let v = Vector2::new(0.0, 5.0); let t = m * v; assert!(nearly_equal(2.0, t.x, 0.001)); assert!(nearly_equal(-5.0, t.y, 0.001)); } }