use std::fmt;
use std::ops::{Mul, MulAssign};
use crate::{UnitVec3, Vec3, EPS};
#[derive(Clone, Copy, PartialEq)]
pub struct Mat3 {
pub col: [Vec3; 3],
}
impl Mat3 {
pub const IDENTITY: Self = Self {
col: [Vec3::X, Vec3::Y, Vec3::Z],
};
pub const ZERO: Self = Self {
col: [Vec3::ZERO, Vec3::ZERO, Vec3::ZERO],
};
#[inline]
pub const fn from_cols(c0: Vec3, c1: Vec3, c2: Vec3) -> Self {
Self { col: [c0, c1, c2] }
}
#[inline]
pub fn from_rows(r0: Vec3, r1: Vec3, r2: Vec3) -> Self {
Self::from_cols(
Vec3::new(r0.x, r1.x, r2.x),
Vec3::new(r0.y, r1.y, r2.y),
Vec3::new(r0.z, r1.z, r2.z),
)
}
pub fn from_axes(right: UnitVec3, up: UnitVec3, forward: UnitVec3) -> Self {
Self::from_cols(right.as_vec(), up.as_vec(), forward.as_vec())
}
pub fn rotation(axis: UnitVec3, angle: f64) -> Self {
let (s, c) = angle.sin_cos();
let t = 1.0 - c;
let Vec3 { x, y, z } = axis.as_vec();
Self::from_rows(
Vec3::new(t * x * x + c, t * x * y - s * z, t * x * z + s * y),
Vec3::new(t * x * y + s * z, t * y * y + c, t * y * z - s * x),
Vec3::new(t * x * z - s * y, t * y * z + s * x, t * z * z + c),
)
}
#[inline]
pub fn transpose(self) -> Self {
Self::from_rows(self.col[0], self.col[1], self.col[2])
}
pub fn det(self) -> f64 {
let [c0, c1, c2] = self.col;
c0.dot(c1.cross(c2))
}
pub fn try_inverse(self) -> Option<Self> {
let d = self.det();
if d.abs() < EPS {
return None;
}
let inv_d = 1.0 / d;
let [c0, c1, c2] = self.col;
let ic0 = Vec3::new(
(c1.y * c2.z - c1.z * c2.y) * inv_d, -(c0.y * c2.z - c0.z * c2.y) * inv_d, (c0.y * c1.z - c0.z * c1.y) * inv_d, );
let ic1 = Vec3::new(
-(c1.x * c2.z - c1.z * c2.x) * inv_d, (c0.x * c2.z - c0.z * c2.x) * inv_d, -(c0.x * c1.z - c0.z * c1.x) * inv_d, );
let ic2 = Vec3::new(
(c1.x * c2.y - c1.y * c2.x) * inv_d, -(c0.x * c2.y - c0.y * c2.x) * inv_d, (c0.x * c1.y - c0.y * c1.x) * inv_d, );
Some(Self::from_cols(ic0, ic1, ic2))
}
#[inline]
pub fn apply(self, v: Vec3) -> Vec3 {
self.col[0] * v.x + self.col[1] * v.y + self.col[2] * v.z
}
}
impl Mul for Mat3 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self::from_cols(
self.apply(rhs.col[0]),
self.apply(rhs.col[1]),
self.apply(rhs.col[2]),
)
}
}
impl MulAssign for Mat3 {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl Mul<Vec3> for Mat3 {
type Output = Vec3;
#[inline]
fn mul(self, v: Vec3) -> Vec3 {
self.apply(v)
}
}
impl fmt::Debug for Mat3 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let [c0, c1, c2] = self.col;
write!(
f,
"Mat3[\n [{:.4} {:.4} {:.4}]\n [{:.4} {:.4} {:.4}]\n [{:.4} {:.4} {:.4}]\n]",
c0.x, c1.x, c2.x, c0.y, c1.y, c2.y, c0.z, c1.z, c2.z,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PI;
#[test]
fn rotation_90_deg_around_z() {
let m = Mat3::rotation(UnitVec3::Z, PI / 2.0);
let v = m * Vec3::X;
assert!((v - Vec3::Y).length() < 1e-12);
}
#[test]
fn inverse_times_original_is_identity() {
let m = Mat3::rotation(
UnitVec3::try_from_vec(Vec3::new(1.0, 2.0, 3.0).normalize()).unwrap(),
1.23,
);
let inv = m.try_inverse().unwrap();
let prod = m * inv;
for i in 0..3 {
let diff = prod.col[i] - Mat3::IDENTITY.col[i];
assert!(diff.length() < 1e-10, "column {i}: {:?}", diff);
}
}
}