use std::ops::Mul;
use crate::MathError;
use crate::vec::Point3;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Mat3(pub [[f64; 3]; 3]);
impl Mat3 {
#[must_use]
pub const fn identity() -> Self {
Self([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
}
#[must_use]
pub const fn transpose(self) -> Self {
let m = &self.0;
Self([
[m[0][0], m[1][0], m[2][0]],
[m[0][1], m[1][1], m[2][1]],
[m[0][2], m[1][2], m[2][2]],
])
}
#[must_use]
pub fn determinant(self) -> f64 {
let m = &self.0;
m[0][0].mul_add(
m[1][1].mul_add(m[2][2], -(m[1][2] * m[2][1])),
m[0][1].mul_add(
m[1][2].mul_add(m[2][0], -(m[1][0] * m[2][2])),
m[0][2] * m[1][0].mul_add(m[2][1], -(m[1][1] * m[2][0])),
),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Mat4(pub [[f64; 4]; 4]);
impl Mat4 {
#[must_use]
pub const fn identity() -> Self {
Self([
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
#[must_use]
pub const fn translation(tx: f64, ty: f64, tz: f64) -> Self {
Self([
[1.0, 0.0, 0.0, tx],
[0.0, 1.0, 0.0, ty],
[0.0, 0.0, 1.0, tz],
[0.0, 0.0, 0.0, 1.0],
])
}
#[must_use]
pub const fn scale(sx: f64, sy: f64, sz: f64) -> Self {
Self([
[sx, 0.0, 0.0, 0.0],
[0.0, sy, 0.0, 0.0],
[0.0, 0.0, sz, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
#[must_use]
pub fn rotation_x(angle: f64) -> Self {
let (s, c) = angle.sin_cos();
Self([
[1.0, 0.0, 0.0, 0.0],
[0.0, c, -s, 0.0],
[0.0, s, c, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
#[must_use]
pub fn rotation_y(angle: f64) -> Self {
let (s, c) = angle.sin_cos();
Self([
[c, 0.0, s, 0.0],
[0.0, 1.0, 0.0, 0.0],
[-s, 0.0, c, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
#[must_use]
pub fn rotation_z(angle: f64) -> Self {
let (s, c) = angle.sin_cos();
Self([
[c, -s, 0.0, 0.0],
[s, c, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
#[must_use]
pub fn mul_point(self, p: Point3) -> Point3 {
let m = &self.0;
Point3::new(
m[0][0].mul_add(
p.x(),
m[0][1].mul_add(p.y(), m[0][2].mul_add(p.z(), m[0][3])),
),
m[1][0].mul_add(
p.x(),
m[1][1].mul_add(p.y(), m[1][2].mul_add(p.z(), m[1][3])),
),
m[2][0].mul_add(
p.x(),
m[2][1].mul_add(p.y(), m[2][2].mul_add(p.z(), m[2][3])),
),
)
}
#[must_use]
pub const fn transpose(self) -> Self {
let m = &self.0;
Self([
[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]],
])
}
#[must_use]
#[allow(clippy::similar_names)]
pub fn determinant(self) -> f64 {
let m = &self.0;
let s0 = m[0][0].mul_add(m[1][1], -(m[1][0] * m[0][1]));
let s1 = m[0][0].mul_add(m[1][2], -(m[1][0] * m[0][2]));
let s2 = m[0][0].mul_add(m[1][3], -(m[1][0] * m[0][3]));
let s3 = m[0][1].mul_add(m[1][2], -(m[1][1] * m[0][2]));
let s4 = m[0][1].mul_add(m[1][3], -(m[1][1] * m[0][3]));
let s5 = m[0][2].mul_add(m[1][3], -(m[1][2] * m[0][3]));
let c5 = m[2][2].mul_add(m[3][3], -(m[3][2] * m[2][3]));
let c4 = m[2][1].mul_add(m[3][3], -(m[3][1] * m[2][3]));
let c3 = m[2][1].mul_add(m[3][2], -(m[3][1] * m[2][2]));
let c2 = m[2][0].mul_add(m[3][3], -(m[3][0] * m[2][3]));
let c1 = m[2][0].mul_add(m[3][2], -(m[3][0] * m[2][2]));
let c0 = m[2][0].mul_add(m[3][1], -(m[3][0] * m[2][1]));
s0.mul_add(
c5,
(-s1).mul_add(
c4,
s2.mul_add(c3, s3.mul_add(c2, (-s4).mul_add(c1, s5 * c0))),
),
)
}
#[allow(clippy::similar_names)]
pub fn inverse(self) -> Result<Self, MathError> {
let m = &self.0;
let s0 = m[0][0].mul_add(m[1][1], -(m[1][0] * m[0][1]));
let s1 = m[0][0].mul_add(m[1][2], -(m[1][0] * m[0][2]));
let s2 = m[0][0].mul_add(m[1][3], -(m[1][0] * m[0][3]));
let s3 = m[0][1].mul_add(m[1][2], -(m[1][1] * m[0][2]));
let s4 = m[0][1].mul_add(m[1][3], -(m[1][1] * m[0][3]));
let s5 = m[0][2].mul_add(m[1][3], -(m[1][2] * m[0][3]));
let c5 = m[2][2].mul_add(m[3][3], -(m[3][2] * m[2][3]));
let c4 = m[2][1].mul_add(m[3][3], -(m[3][1] * m[2][3]));
let c3 = m[2][1].mul_add(m[3][2], -(m[3][1] * m[2][2]));
let c2 = m[2][0].mul_add(m[3][3], -(m[3][0] * m[2][3]));
let c1 = m[2][0].mul_add(m[3][2], -(m[3][0] * m[2][2]));
let c0 = m[2][0].mul_add(m[3][1], -(m[3][0] * m[2][1]));
let det = s0.mul_add(
c5,
(-s1).mul_add(
c4,
s2.mul_add(c3, s3.mul_add(c2, (-s4).mul_add(c1, s5 * c0))),
),
);
let max_minor = s0
.abs()
.max(s1.abs())
.max(s2.abs())
.max(s3.abs())
.max(s4.abs())
.max(s5.abs())
.max(c0.abs())
.max(c1.abs())
.max(c2.abs())
.max(c3.abs())
.max(c4.abs())
.max(c5.abs());
if max_minor == 0.0 {
return Err(MathError::SingularMatrix);
}
if det.abs() < f64::EPSILON * max_minor * max_minor {
return Err(MathError::SingularMatrix);
}
let inv_det = 1.0 / det;
Ok(Self([
[
m[1][1].mul_add(c5, m[1][3].mul_add(c3, -(m[1][2] * c4))) * inv_det,
(-m[0][1]).mul_add(c5, m[0][2].mul_add(c4, -(m[0][3] * c3))) * inv_det,
m[3][1].mul_add(s5, m[3][3].mul_add(s3, -(m[3][2] * s4))) * inv_det,
(-m[2][1]).mul_add(s5, m[2][2].mul_add(s4, -(m[2][3] * s3))) * inv_det,
],
[
(-m[1][0]).mul_add(c5, m[1][2].mul_add(c2, -(m[1][3] * c1))) * inv_det,
m[0][0].mul_add(c5, m[0][3].mul_add(c1, -(m[0][2] * c2))) * inv_det,
(-m[3][0]).mul_add(s5, m[3][2].mul_add(s2, -(m[3][3] * s1))) * inv_det,
m[2][0].mul_add(s5, m[2][3].mul_add(s1, -(m[2][2] * s2))) * inv_det,
],
[
m[1][0].mul_add(c4, m[1][3].mul_add(c0, -(m[1][1] * c2))) * inv_det,
(-m[0][0]).mul_add(c4, m[0][1].mul_add(c2, -(m[0][3] * c0))) * inv_det,
m[3][0].mul_add(s4, m[3][3].mul_add(s0, -(m[3][1] * s2))) * inv_det,
(-m[2][0]).mul_add(s4, m[2][1].mul_add(s2, -(m[2][3] * s0))) * inv_det,
],
[
(-m[1][0]).mul_add(c3, m[1][1].mul_add(c1, -(m[1][2] * c0))) * inv_det,
m[0][0].mul_add(c3, m[0][2].mul_add(c0, -(m[0][1] * c1))) * inv_det,
(-m[3][0]).mul_add(s3, m[3][1].mul_add(s1, -(m[3][2] * s0))) * inv_det,
m[2][0].mul_add(s3, m[2][2].mul_add(s0, -(m[2][1] * s1))) * inv_det,
],
]))
}
}
impl Mul for Mat4 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let a = &self.0;
let b = &rhs.0;
let mut out = [[0.0_f64; 4]; 4];
for i in 0..4 {
for j in 0..4 {
out[i][j] = a[i][0].mul_add(
b[0][j],
a[i][1].mul_add(b[1][j], a[i][2].mul_add(b[2][j], a[i][3] * b[3][j])),
);
}
}
Self(out)
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
fn approx_eq_mat4(a: &Mat4, b: &Mat4, tol: f64) -> bool {
for i in 0..4 {
for j in 0..4 {
if (a.0[i][j] - b.0[i][j]).abs() > tol {
return false;
}
}
}
true
}
#[test]
fn identity_inverse() {
let inv = Mat4::identity().inverse().expect("invertible");
assert!(approx_eq_mat4(&inv, &Mat4::identity(), 1e-14));
}
#[test]
fn translation_inverse() {
let m = Mat4::translation(1.0, 2.0, 3.0);
let inv = m.inverse().expect("invertible");
let product = m * inv;
assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-12));
}
#[test]
fn rotation_inverse() {
let m = Mat4::rotation_x(0.7) * Mat4::rotation_y(1.2) * Mat4::rotation_z(0.3);
let inv = m.inverse().expect("invertible");
let product = m * inv;
assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-12));
}
#[test]
fn scale_inverse() {
let m = Mat4::scale(2.0, 3.0, 4.0);
let inv = m.inverse().expect("invertible");
let product = m * inv;
assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-12));
}
#[test]
fn singular_matrix() {
let m = Mat4([[1.0, 0.0, 0.0, 0.0]; 4]);
assert!(m.inverse().is_err());
}
#[test]
fn combined_transform_inverse() {
let m = Mat4::translation(5.0, -3.0, 2.0)
* Mat4::rotation_z(std::f64::consts::FRAC_PI_4)
* Mat4::scale(2.0, 0.5, 1.0);
let inv = m.inverse().expect("invertible");
let product = m * inv;
assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-10));
}
use proptest::prelude::*;
proptest! {
#[test]
fn prop_inverse_roundtrip(
tx in -10.0f64..10.0,
ty in -10.0f64..10.0,
tz in -10.0f64..10.0,
angle in 0.0f64..std::f64::consts::TAU,
) {
let m = Mat4::translation(tx, ty, tz) * Mat4::rotation_z(angle);
let inv = m.inverse().expect("invertible");
let product = m * inv;
prop_assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-10));
}
#[test]
fn prop_inverse_scaled(
tx in -10.0f64..10.0,
ty in -10.0f64..10.0,
tz in -10.0f64..10.0,
angle in 0.0f64..std::f64::consts::TAU,
scale_exp in prop::sample::select(&[-8_i32, -6, -4, -2, 2, 4, 6][..]),
) {
let scale = 10.0_f64.powi(scale_exp);
let m = Mat4::translation(tx * scale, ty * scale, tz * scale)
* Mat4::rotation_z(angle)
* Mat4::scale(scale, scale, scale);
let inv = m.inverse().expect("invertible");
let product = m * inv;
prop_assert!(approx_eq_mat4(&product, &Mat4::identity(), 1e-6));
}
}
}