use std::fmt;
use std::ops::{Index, IndexMut, Mul, Sub};
use crate::{tuples::Tuple, Float, EPSILON};
/// Short hand for creating a Matrix4x4 set to the identity matrix.
pub fn identity() -> Matrix4x4 {
Matrix4x4::identity()
}
/// Short hand for creating a Matrix4x4 for rotating around the X-axis.
pub fn rotation_x(radians: Float) -> Matrix4x4 {
Matrix4x4::rotation_x(radians)
}
/// Short hand for creating a Matrix4x4 for rotating around the Y-axis.
pub fn rotation_y(radians: Float) -> Matrix4x4 {
Matrix4x4::rotation_y(radians)
}
/// Short hand for creating a Matrix4x4 for rotating around the Z-axis.
pub fn rotation_z(radians: Float) -> Matrix4x4 {
Matrix4x4::rotation_z(radians)
}
/// Short hand for creating a Matrix4x4 that scales in the given x,y,z axis.
pub fn scaling(x: Float, y: Float, z: Float) -> Matrix4x4 {
Matrix4x4::scaling(x, y, z)
}
/// Short hand for creating a Matrix4x4 that shears across the given axis pairs.
pub fn shearing(xy: Float, xz: Float, yx: Float, yz: Float, zx: Float, zy: Float) -> Matrix4x4 {
Matrix4x4::shearing(xy, xz, yx, yz, zx, zy)
}
/// Short hand for creating a Matrix4x4 that translations along the given x,y,z axis.
pub fn translation(x: Float, y: Float, z: Float) -> Matrix4x4 {
Matrix4x4::translation(x, y, z)
}
#[derive(Debug)]
pub struct Matrix2x2 {
m: [[Float; 2]; 2],
}
impl Matrix2x2 {
/// Create a `Matrix2x2` with each of the given rows.
pub fn new(r0: [Float; 2], r1: [Float; 2]) -> Matrix2x2 {
Matrix2x2 { m: [r0, r1] }
}
/// Calculate the determinant of a 2x2.
pub fn determinant(&self) -> Float {
let m = self;
m[(0, 0)] * m[(1, 1)] - m[(0, 1)] * m[(1, 0)]
}
}
impl Index<(usize, usize)> for Matrix2x2 {
type Output = Float;
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
&self.m[row][col]
}
}
impl PartialEq for Matrix2x2 {
fn eq(&self, rhs: &Matrix2x2) -> bool {
let l = self.m;
let r = rhs.m;
for i in 0..2 {
for j in 0..2 {
let d = (l[i][j] - r[i][j]).abs();
if d > EPSILON {
return false;
}
}
}
true
}
}
#[derive(Debug)]
pub struct Matrix3x3 {
m: [[Float; 3]; 3],
}
impl Matrix3x3 {
/// Create a `Matrix3x2` with each of the given rows.
pub fn new(r0: [Float; 3], r1: [Float; 3], r2: [Float; 3]) -> Matrix3x3 {
Matrix3x3 { m: [r0, r1, r2] }
}
/// submatrix extracts a 2x2 matrix ignoring the 0-based `row` and `col` given.
pub fn submatrix(&self, row: usize, col: usize) -> Matrix2x2 {
assert!(row < 3);
assert!(col < 3);
let mut rows = vec![];
for r in 0..3 {
if r != row {
let mut v = vec![];
for c in 0..3 {
if c != col {
v.push(self[(r, c)]);
}
}
rows.push(v);
}
}
let m = [[rows[0][0], rows[0][1]], [rows[1][0], rows[1][1]]];
Matrix2x2 { m }
}
/// Compute minor of a 3x3 matrix.
pub fn minor(&self, row: usize, col: usize) -> Float {
self.submatrix(row, col).determinant()
}
/// Compute cofactor of a 3x3 matrix.
pub fn cofactor(&self, row: usize, col: usize) -> Float {
let negate = if (row + col) % 2 == 0 { 1. } else { -1. };
self.submatrix(row, col).determinant() * negate
}
/// Compute determinant of a 3x3 matrix.
pub fn determinant(&self) -> Float {
(0..3).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
}
}
impl Index<(usize, usize)> for Matrix3x3 {
type Output = Float;
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
&self.m[row][col]
}
}
impl PartialEq for Matrix3x3 {
fn eq(&self, rhs: &Matrix3x3) -> bool {
let l = self.m;
let r = rhs.m;
for i in 0..3 {
for j in 0..3 {
let d = (l[i][j] - r[i][j]).abs();
if d > EPSILON {
return false;
}
}
}
true
}
}
/// Matrix4x4 represents a 4x4 matrix in row-major form. So, element `m[i][j]` corresponds to mi,j
/// where `i` is the row number and `j` is the column number.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// float::consts::PI,
/// matrices::Matrix4x4,
/// tuples::{point, vector},
/// };
///
/// // Individual transformations are applied in sequence.
/// let p = point(1., 0., 1.);
/// let a = Matrix4x4::rotation_x(PI / 2.);
/// let b = Matrix4x4::scaling(5., 5., 5.);
/// let c = Matrix4x4::translation(10., 5., 7.);
/// // Apply rotation first.
/// let p2 = a * p;
/// assert_eq!(p2, point(1., -1., 0.));
/// // Then apply scaling.
/// let p3 = b * p2;
/// assert_eq!(p3, point(5., -5., 0.));
/// // Then apply translation.
/// let p4 = c * p3;
/// assert_eq!(p4, point(15., 0., 7.));
///
/// // Chained transformations must be applied in reverse order.
/// let p = point(1., 0., 1.);
/// let a = Matrix4x4::rotation_x(PI / 2.);
/// let b = Matrix4x4::scaling(5., 5., 5.);
/// let c = Matrix4x4::translation(10., 5., 7.);
/// let t = c * b * a;
/// assert_eq!(t * p, point(15., 0., 7.));
/// ```
#[derive(Copy, Clone, Default)]
pub struct Matrix4x4 {
m: [[Float; 4]; 4],
}
impl From<[Float; 16]> for Matrix4x4 {
fn from(t: [Float; 16]) -> Self {
Matrix4x4 {
m: [
[t[0], t[1], t[2], t[3]],
[t[4], t[5], t[6], t[7]],
[t[8], t[9], t[10], t[11]],
[t[12], t[13], t[14], t[15]],
],
}
}
}
impl Matrix4x4 {
/// Create a `Matrix4x4` containing the identity, all zeros with ones along the diagonal.
pub fn identity() -> Matrix4x4 {
Matrix4x4::new(
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.],
)
}
/// Create a `Matrix4x4` with each of the given rows.
pub fn new(r0: [Float; 4], r1: [Float; 4], r2: [Float; 4], r3: [Float; 4]) -> Matrix4x4 {
Matrix4x4 {
m: [r0, r1, r2, r3],
}
}
/// Creates a 4x4 matrix representing a translation of x,y,z.
pub fn translation(x: Float, y: Float, z: Float) -> Matrix4x4 {
Matrix4x4::new(
[1., 0., 0., x],
[0., 1., 0., y],
[0., 0., 1., z],
[0., 0., 0., 1.],
)
}
/// Creates a 4x4 matrix representing a scaling of x,y,z.
pub fn scaling(x: Float, y: Float, z: Float) -> Matrix4x4 {
Matrix4x4::new(
[x, 0., 0., 0.],
[0., y, 0., 0.],
[0., 0., z, 0.],
[0., 0., 0., 1.],
)
}
/// Creates a 4x4 matrix representing a rotation around the x-axis.
pub fn rotation_x(radians: Float) -> Matrix4x4 {
let r = radians;
Matrix4x4::new(
[1., 0., 0., 0.],
[0., r.cos(), -r.sin(), 0.],
[0., r.sin(), r.cos(), 0.],
[0., 0., 0., 1.],
)
}
/// Creates a 4x4 matrix representing a rotation around the y-axis.
pub fn rotation_y(radians: Float) -> Matrix4x4 {
let r = radians;
Matrix4x4::new(
[r.cos(), 0., r.sin(), 0.],
[0., 1., 0., 0.],
[-r.sin(), 0., r.cos(), 0.],
[0., 0., 0., 1.],
)
}
/// Creates a 4x4 matrix representing a rotation around the z-axis.
pub fn rotation_z(radians: Float) -> Matrix4x4 {
let r = radians;
Matrix4x4::new(
[r.cos(), -r.sin(), 0., 0.],
[r.sin(), r.cos(), 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.],
)
}
/// Transpose self, returning a new matrix that has been reflected across the diagonal.
pub fn transpose(&self) -> Matrix4x4 {
let m = self.m;
Matrix4x4 {
m: [
[m[0][0], m[1][0], m[2][0], m[3][0]],
[m[0][1], m[1][1], m[2][1], m[3][1]],
[m[0][2], m[1][2], m[2][2], m[3][2]],
[m[0][3], m[1][3], m[2][3], m[3][3]],
],
}
}
/// Create a transform matrix that will shear (skew) points.
/// # Examples
pub fn shearing(xy: Float, xz: Float, yx: Float, yz: Float, zx: Float, zy: Float) -> Matrix4x4 {
Matrix4x4::new(
[1., xy, xz, 0.],
[yx, 1., yz, 0.],
[zx, zy, 1., 0.],
[0., 0., 0., 1.],
)
}
/// Returns a new matrix that is the inverse of self. If self is A, inverse returns A-1, where
/// AA-1 = I.
/// This implementation uses a numerically stable Gauss–Jordan elimination routine to compute the inverse.
pub fn inverse_rtiow(&self) -> Matrix4x4 {
// TODO(wathiede): how come the C++ version doesn't need to deal with non-invertable
// matrix.
let mut indxc: [usize; 4] = Default::default();
let mut indxr: [usize; 4] = Default::default();
let mut ipiv: [usize; 4] = Default::default();
let mut minv = self.m;
for i in 0..4 {
let mut irow: usize = 0;
let mut icol: usize = 0;
let mut big: Float = 0.;
// Choose pivot
for j in 0..4 {
if ipiv[j] != 1 {
for (k, ipivk) in ipiv.iter().enumerate() {
if *ipivk == 0 {
if minv[j][k].abs() >= big {
big = minv[j][k].abs();
irow = j;
icol = k;
}
} else if *ipivk > 1 {
eprintln!("Singular matrix in MatrixInvert");
}
}
}
}
ipiv[icol] += 1;
// Swap rows _irow_ and _icol_ for pivot
if irow != icol {
// Can't figure out how to make swap work here.
#[allow(clippy::manual_swap)]
for k in 0..4 {
let tmp = minv[irow][k];
minv[irow][k] = minv[icol][k];
minv[icol][k] = tmp;
}
}
indxr[i] = irow;
indxc[i] = icol;
if minv[icol][icol] == 0. {
eprintln!("Singular matrix in MatrixInvert");
}
// Set $m[icol][icol]$ to one by scaling row _icol_ appropriately
let pivinv: Float = minv[icol][icol].recip();
minv[icol][icol] = 1.;
for j in 0..4 {
minv[icol][j] *= pivinv;
}
// Subtract this row from others to zero out their columns
for j in 0..4 {
if j != icol {
let save = minv[j][icol];
minv[j][icol] = 0.;
for k in 0..4 {
minv[j][k] -= minv[icol][k] * save;
}
}
}
}
// Swap columns to reflect permutation
for j in (0..4).rev() {
if indxr[j] != indxc[j] {
for mi in &mut minv {
mi.swap(indxr[j], indxc[j])
}
}
}
Matrix4x4 { m: minv }
}
/// submatrix extracts a 3x3 matrix ignoring the 0-based `row` and `col` given.
pub fn submatrix(&self, row: usize, col: usize) -> Matrix3x3 {
assert!(row < 4);
assert!(col < 4);
let mut rows = vec![];
for r in 0..4 {
if r != row {
let mut v = vec![];
for c in 0..4 {
if c != col {
v.push(self[(r, c)]);
}
}
rows.push(v);
}
}
let m = [
[rows[0][0], rows[0][1], rows[0][2]],
[rows[1][0], rows[1][1], rows[1][2]],
[rows[2][0], rows[2][1], rows[2][2]],
];
Matrix3x3 { m }
}
/// Compute minor of a 4x4 matrix.
pub fn minor(&self, row: usize, col: usize) -> Float {
self.submatrix(row, col).determinant()
}
/// Compute cofactor of a 4x4 matrix.
pub fn cofactor(&self, row: usize, col: usize) -> Float {
let negate = if (row + col) % 2 == 0 { 1. } else { -1. };
self.submatrix(row, col).determinant() * negate
}
/// Compute determinant of a 4x4 matrix.
pub fn determinant(&self) -> Float {
(0..4).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
}
/// Compute invertibility of matrix (i.e. non-zero determinant.
pub fn invertable(&self) -> bool {
self.determinant() != 0.
}
/// Compute the inverse of a 4x4 matrix.
pub fn inverse(&self) -> Matrix4x4 {
self.inverse_rtc()
}
pub fn inverse_rtc(&self) -> Matrix4x4 {
let m = self;
if !m.invertable() {
panic!("Matrix4x4::inverse called on matrix with determinant() == 0");
}
let mut m2 = Matrix4x4::identity();
let det = m.determinant();
for row in 0..4 {
for col in 0..4 {
let c = m.cofactor(row, col);
m2[(col, row)] = c / det;
}
}
m2
}
}
impl fmt::Debug for Matrix4x4 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(
f,
"\n {:8.5?}\n {:8.5?}\n {:8.5?}\n {:8.5?}",
self.m[0], self.m[1], self.m[2], self.m[3]
)
} else {
write!(
f,
"[{:?} {:?} {:?} {:?}]",
self.m[0], self.m[1], self.m[2], self.m[3]
)
}
}
}
impl Mul for Matrix4x4 {
type Output = Matrix4x4;
/// Implement matrix multiplication for `Matrix4x4`.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix4x4;
///
/// let i = Matrix4x4::identity();
/// let m1 = Matrix4x4::identity();
/// let m2 = Matrix4x4::identity();
///
/// assert_eq!(m1 * m2, i);
/// ```
fn mul(self, m2: Matrix4x4) -> Matrix4x4 {
let m1 = self;
let mut r: Matrix4x4 = Default::default();
for i in 0..4 {
for j in 0..4 {
r.m[i][j] = m1.m[i][0] * m2.m[0][j]
+ m1.m[i][1] * m2.m[1][j]
+ m1.m[i][2] * m2.m[2][j]
+ m1.m[i][3] * m2.m[3][j];
}
}
r
}
}
impl Mul for Matrix4x4 {
type Output = Tuple;
/// Implement matrix multiplication for `Matrix4x4` * `Tuple`.
///
/// # Examples
/// ```
/// use rtchallenge::matrices::Matrix4x4;
/// use rtchallenge::tuples::Tuple;
///
/// let a = Matrix4x4::new(
/// [1., 2., 3., 4.],
/// [2., 4., 4., 2.],
/// [8., 6., 4., 1.],
/// [0., 0., 0., 1.],
/// );
/// let b = Tuple::new(1., 2., 3., 1.);
///
/// assert_eq!(a * b, Tuple::new(18., 24., 33., 1.));
/// ```
fn mul(self, t: Tuple) -> Tuple {
let m = self;
Tuple {
x: m.m[0][0] * t.x + m.m[0][1] * t.y + m.m[0][2] * t.z + m.m[0][3] * t.w,
y: m.m[1][0] * t.x + m.m[1][1] * t.y + m.m[1][2] * t.z + m.m[1][3] * t.w,
z: m.m[2][0] * t.x + m.m[2][1] * t.y + m.m[2][2] * t.z + m.m[2][3] * t.w,
w: m.m[3][0] * t.x + m.m[3][1] * t.y + m.m[3][2] * t.z + m.m[3][3] * t.w,
}
}
}
impl Sub for Matrix4x4 {
type Output = Matrix4x4;
fn sub(self, m2: Matrix4x4) -> Matrix4x4 {
let m1 = self;
let mut r: Matrix4x4 = Default::default();
for i in 0..4 {
for j in 0..4 {
r.m[i][j] = m1.m[i][j] - m2.m[i][j];
}
}
r
}
}
impl PartialEq for Matrix4x4 {
fn eq(&self, rhs: &Matrix4x4) -> bool {
let l = self.m;
let r = rhs.m;
for i in 0..4 {
for j in 0..4 {
let d = (l[i][j] - r[i][j]).abs();
if d > EPSILON {
return false;
}
}
}
true
}
}
impl Index<(usize, usize)> for Matrix4x4 {
type Output = Float;
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
&self.m[row][col]
}
}
impl IndexMut<(usize, usize)> for Matrix4x4 {
fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output {
&mut self.m[row][col]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
float::consts::PI,
tuples::{point, vector},
};
#[test]
fn translation() {
let transform = Matrix4x4::translation(5., -3., 2.);
let p = point(-3., 4., 5.);
assert_eq!(transform * p, point(2., 1., 7.));
let inv = transform.inverse();
assert_eq!(inv * p, point(-8., 7., 3.));
let v = vector(-3., 4., 5.);
assert_eq!(transform * v, v);
}
#[test]
fn scaling() {
// A scaling matrix applied to a point.
let transform = Matrix4x4::scaling(2., 3., 4.);
let p = point(-4., 6., 8.);
assert_eq!(transform * p, point(-8., 18., 32.));
// A scaling matrix applied to a vector.
let v = vector(-4., 6., 8.);
assert_eq!(transform * v, vector(-8., 18., 32.));
// Multiplying by the inverse of a scaling matrix.
let inv = transform.inverse();
assert_eq!(inv * v, vector(-2., 2., 2.));
// Reflection is scaling by a negative value.
let transform = Matrix4x4::scaling(-1., 1., 1.);
let p = point(2., 3., 4.);
assert_eq!(transform * p, point(-2., 3., 4.));
}
#[test]
fn rotation_x() {
// A scaling matrix applied to a point.
let p = point(0., 1., 0.);
let half_quarter = Matrix4x4::rotation_x(PI / 4.);
let full_quarter = Matrix4x4::rotation_x(PI / 2.);
assert_eq!(
half_quarter * p,
point(0., (2.0 as Float).sqrt() / 2., (2.0 as Float).sqrt() / 2.)
);
assert_eq!(full_quarter * p, point(0., 0., 1.),);
}
#[test]
fn rotation_y() {
// A scaling matrix applied to a point.
let p = point(0., 0., 1.);
let half_quarter = Matrix4x4::rotation_y(PI / 4.);
let full_quarter = Matrix4x4::rotation_y(PI / 2.);
assert_eq!(
half_quarter * p,
point((2.0 as Float).sqrt() / 2., 0., (2.0 as Float).sqrt() / 2.)
);
assert_eq!(full_quarter * p, point(1., 0., 0.,),);
}
#[test]
fn rotation_z() {
// A scaling matrix applied to a point.
let p = point(0., 1., 0.);
let half_quarter = Matrix4x4::rotation_z(PI / 4.);
let full_quarter = Matrix4x4::rotation_z(PI / 2.);
assert_eq!(
half_quarter * p,
point(-(2.0 as Float).sqrt() / 2., (2.0 as Float).sqrt() / 2., 0.)
);
assert_eq!(full_quarter * p, point(-1., 0., 0.,),);
}
#[test]
fn transpose() {
let m = Matrix4x4::new(
[2., 0., 0., 0.],
[3., 1., 0., 0.],
[4., 0., 1., 0.],
[5., 6., 7., 1.],
);
let m_t = Matrix4x4::new(
[2., 3., 4., 5.],
[0., 1., 0., 6.],
[0., 0., 1., 7.],
[0., 0., 0., 1.],
);
assert_eq!(m.transpose(), m_t);
assert_eq!(Matrix4x4::identity(), Matrix4x4::identity().transpose());
}
#[test]
fn shearing() {
// A shearing transform moves x in proportion to y.
let transform = Matrix4x4::shearing(1., 0., 0., 0., 0., 0.);
let p = point(2., 3., 4.);
assert_eq!(transform * p, point(5., 3., 4.));
// A shearing transform moves x in proportion to z.
let transform = Matrix4x4::shearing(0., 1., 0., 0., 0., 0.);
let p = point(2., 3., 4.);
assert_eq!(transform * p, point(6., 3., 4.));
// A shearing transform moves y in proportion to x.
let transform = Matrix4x4::shearing(0., 0., 1., 0., 0., 0.);
let p = point(2., 3., 4.);
assert_eq!(transform * p, point(2., 5., 4.));
// A shearing transform moves y in proportion to z.
let transform = Matrix4x4::shearing(0., 0., 0., 1., 0., 0.);
let p = point(2., 3., 4.);
assert_eq!(transform * p, point(2., 7., 4.));
// A shearing transform moves z in proportion to x.
let transform = Matrix4x4::shearing(0., 0., 0., 0., 1., 0.);
let p = point(2., 3., 4.);
assert_eq!(transform * p, point(2., 3., 6.));
// A shearing transform moves z in proportion to y.
let transform = Matrix4x4::shearing(0., 0., 0., 0., 0., 1.);
let p = point(2., 3., 4.);
assert_eq!(transform * p, point(2., 3., 7.));
}
#[test]
fn inverse_rtiow() {
let i = Matrix4x4::identity();
assert_eq!(i.inverse_rtiow() * i, i);
let m = Matrix4x4::new(
[2., 0., 0., 0.],
[0., 3., 0., 0.],
[0., 0., 4., 0.],
[0., 0., 0., 1.],
);
assert_eq!(m.inverse_rtiow() * m, i);
assert_eq!(m * m.inverse_rtiow(), i);
}
#[test]
fn determinant_2x2() {
let a = Matrix2x2::new([1., 5.], [-3., 2.]);
assert_eq!(a.determinant(), 17.);
}
#[test]
fn determinant_3x3() {
let a = Matrix3x3::new([1., 2., 6.], [-5., 8., -4.], [2., 6., 4.]);
assert_eq!(a.cofactor(0, 0), 56.);
assert_eq!(a.cofactor(0, 1), 12.);
assert_eq!(a.cofactor(0, 2), -46.);
assert_eq!(a.determinant(), -196.);
}
#[test]
fn determinant_4x4() {
let a = Matrix4x4::new(
[-2., -8., 3., 5.],
[-3., 1., 7., 3.],
[1., 2., -9., 6.],
[-6., 7., 7., -9.],
);
assert_eq!(a.cofactor(0, 0), 690.);
assert_eq!(a.cofactor(0, 1), 447.);
assert_eq!(a.cofactor(0, 2), 210.);
assert_eq!(a.cofactor(0, 3), 51.);
assert_eq!(a.determinant(), -4071.);
}
#[test]
fn submatrix_3x3() {
assert_eq!(
Matrix3x3::new([1., 5., 0.], [-3., 2., 7.], [0., 6., -3.],).submatrix(0, 2),
Matrix2x2::new([-3., 2.], [0., 6.])
);
}
#[test]
fn submatrix_4x4() {
assert_eq!(
Matrix4x4::new(
[-6., 1., 1., 6.],
[-8., 5., 8., 6.],
[-1., 0., 8., 2.],
[-7., 1., -1., 1.],
)
.submatrix(2, 1),
Matrix3x3::new([-6., 1., 6.], [-8., 8., 6.], [-7., -1., 1.],)
);
}
#[test]
fn minor_3x3() {
let a = Matrix3x3::new([3., 5., 0.], [2., -1., -7.], [6., -1., 5.]);
let b = a.submatrix(1, 0);
assert_eq!(b.determinant(), 25.0);
assert_eq!(b.determinant(), a.minor(1, 0));
}
#[test]
fn cofactor_3x3() {
let a = Matrix3x3::new([3., 5., 0.], [2., -1., -7.], [6., -1., 5.]);
assert_eq!(a.minor(0, 0), -12.);
assert_eq!(a.cofactor(0, 0), -12.);
assert_eq!(a.minor(1, 0), 25.);
assert_eq!(a.cofactor(1, 0), -25.);
}
#[test]
fn construct2x2() {
let m = Matrix2x2::new([-3., 5.], [1., -2.]);
assert_eq!(m[(0, 0)], -3.);
assert_eq!(m[(0, 1)], 5.);
assert_eq!(m[(1, 0)], 1.);
assert_eq!(m[(1, 1)], -2.);
}
#[test]
fn construct3x3() {
let m = Matrix3x3::new([-3., 5., 0.], [1., -2., -7.], [0., 1., 1.]);
assert_eq!(m[(0, 0)], -3.);
assert_eq!(m[(1, 1)], -2.);
assert_eq!(m[(2, 2)], 1.);
}
#[test]
fn invertable() {
let a = Matrix4x4::new(
[6., 4., 4., 4.],
[5., 5., 7., 6.],
[4., -9., 3., -7.],
[9., 1., 7., -6.],
);
assert_eq!(a.determinant(), -2120.);
assert_eq!(a.invertable(), true);
let a = Matrix4x4::new(
[-4., 2., -2., -3.],
[9., 6., 2., 6.],
[0., -5., 1., -5.],
[0., 0., 0., 0.],
);
assert_eq!(a.determinant(), 0.);
assert_eq!(a.invertable(), false);
}
#[test]
fn inverse() {
let a = Matrix4x4::new(
[-5., 2., 6., -8.],
[1., -5., 1., 8.],
[7., 7., -6., -7.],
[1., -3., 7., 4.],
);
let b = a.inverse();
assert_eq!(a.determinant(), 532.);
assert_eq!(a.cofactor(2, 3), -160.);
assert_eq!(b[(3, 2)], -160. / 532.);
assert_eq!(a.cofactor(3, 2), 105.);
assert_eq!(b[(2, 3)], 105. / 532.);
assert_eq!(
b,
Matrix4x4::new(
[0.21804512, 0.45112783, 0.24060151, -0.04511278],
[-0.8082707, -1.456767, -0.44360903, 0.5206767],
[-0.078947365, -0.2236842, -0.05263158, 0.19736843],
[-0.52255636, -0.81390977, -0.30075186, 0.30639097]
)
);
// Second test case
assert_eq!(
Matrix4x4::new(
[8., -5., 9., 2.],
[7., 5., 6., 1.],
[-6., 0., 9., 6.],
[-3., 0., -9., -4.],
)
.inverse(),
Matrix4x4::new(
[-0.15384616, -0.15384616, -0.2820513, -0.53846157],
[-0.07692308, 0.12307692, 0.025641026, 0.03076923],
[0.35897437, 0.35897437, 0.43589744, 0.9230769],
[-0.6923077, -0.6923077, -0.7692308, -1.9230769]
),
);
// Third test case
assert_eq!(
Matrix4x4::new(
[9., 3., 0., 9.],
[-5., -2., -6., -3.],
[-4., 9., 6., 4.],
[-7., 6., 6., 2.],
)
.inverse(),
Matrix4x4::new(
[-0.04074074, -0.07777778, 0.14444445, -0.22222222],
[-0.07777778, 0.033333335, 0.36666667, -0.33333334],
[-0.029012345, -0.14629629, -0.10925926, 0.12962963],
[0.17777778, 0.06666667, -0.26666668, 0.33333334]
),
);
let a = Matrix4x4::new(
[3., -9., 7., 3.],
[3., -8., 2., -9.],
[-4., 4., 4., 1.],
[-6., 5., -1., 1.],
);
let b = Matrix4x4::new(
[8., 2., 2., 2.],
[3., -1., 7., 0.],
[7., 0., 5., 4.],
[6., -2., 0., 5.],
);
let c = a * b;
assert_eq!(c * b.inverse(), a);
}
#[test]
fn construct4x4() {
let m = Matrix4x4::new(
[1., 2., 3., 4.],
[5.5, 6.5, 7.5, 8.5],
[9., 10., 11., 12.],
[13.5, 14.5, 15.5, 16.5],
);
assert_eq!(m[(0, 0)], 1.);
assert_eq!(m[(0, 3)], 4.);
assert_eq!(m[(1, 0)], 5.5);
assert_eq!(m[(1, 2)], 7.5);
assert_eq!(m[(2, 2)], 11.);
assert_eq!(m[(3, 0)], 13.5);
assert_eq!(m[(3, 2)], 15.5);
}
#[test]
fn equality4x4() {
let a = Matrix4x4::new(
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 8., 7., 6.],
[5., 4., 3., 2.],
);
let b = Matrix4x4::new(
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 8., 7., 6.],
[5., 4., 3., 2.],
);
assert_eq!(a, b);
}
#[test]
fn inequality4x4() {
let a = Matrix4x4::new(
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 8., 7., 6.],
[5., 4., 3., 2.],
);
let b = Matrix4x4::new(
[2., 3., 4., 5.],
[6., 7., 8., 9.],
[8., 7., 6., 5.],
[4., 3., 2., 1.],
);
assert_ne!(a, b);
}
#[test]
fn mul4x4() {
let a = Matrix4x4::new(
[1., 2., 3., 4.],
[5., 6., 7., 8.],
[9., 8., 7., 6.],
[5., 4., 3., 2.],
);
let b = Matrix4x4::new(
[-2., 1., 2., 3.],
[3., 2., 1., -1.],
[4., 3., 6., 5.],
[1., 2., 7., 8.],
);
assert_eq!(
a * b,
Matrix4x4::new(
[20., 22., 50., 48.],
[44., 54., 114., 108.],
[40., 58., 110., 102.],
[16., 26., 46., 42.],
)
);
}
}