use super::quat::Quat;
use super::vec3::Vec3;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct Mat3 {
pub(crate) cols: [Vec3; 3],
}
impl Default for Mat3 {
fn default() -> Self {
Self::ZERO
}
}
impl Mat3 {
pub(crate) const ZERO: Mat3 = Mat3 {
cols: [Vec3::ZERO; 3],
};
#[cfg(test)]
pub(crate) const IDENTITY: Mat3 = Mat3 {
cols: [Vec3::X, Vec3::Y, Vec3::Z],
};
pub(crate) const fn from_cols(x: Vec3, y: Vec3, z: Vec3) -> Self {
Mat3 { cols: [x, y, z] }
}
#[cfg(test)]
pub(crate) const fn from_diagonal(d: Vec3) -> Self {
Mat3 {
cols: [
Vec3 {
x: d.x,
y: 0.0,
z: 0.0,
},
Vec3 {
x: 0.0,
y: d.y,
z: 0.0,
},
Vec3 {
x: 0.0,
y: 0.0,
z: d.z,
},
],
}
}
pub(crate) fn from_quat(q: Quat) -> Self {
Mat3 {
cols: [q.rotate(Vec3::X), q.rotate(Vec3::Y), q.rotate(Vec3::Z)],
}
}
pub(crate) fn diagonal_conjugated(q: Quat, d: Vec3) -> Self {
let r = Self::from_quat(q);
let scaled = [r.cols[0] * d.x, r.cols[1] * d.y, r.cols[2] * d.z];
Mat3 {
cols: [
scaled[0] * r.cols[0].x + scaled[1] * r.cols[1].x + scaled[2] * r.cols[2].x,
scaled[0] * r.cols[0].y + scaled[1] * r.cols[1].y + scaled[2] * r.cols[2].y,
scaled[0] * r.cols[0].z + scaled[1] * r.cols[1].z + scaled[2] * r.cols[2].z,
],
}
}
pub(crate) fn mul_vec3(&self, v: Vec3) -> Vec3 {
self.cols[0] * v.x + self.cols[1] * v.y + self.cols[2] * v.z
}
pub(crate) fn add(&self, other: Mat3) -> Mat3 {
Mat3 {
cols: [
self.cols[0] + other.cols[0],
self.cols[1] + other.cols[1],
self.cols[2] + other.cols[2],
],
}
}
pub(crate) fn inverse(&self) -> Mat3 {
let [a, b, c] = self.cols;
let (r0, r1, r2) = (b.cross(c), c.cross(a), a.cross(b));
let determinant = a.dot(r0);
if determinant.abs() <= f32::MIN_POSITIVE {
return Mat3::ZERO;
}
let inv = 1.0 / determinant;
Mat3 {
cols: [
super::vec3::vec3(r0.x, r1.x, r2.x) * inv,
super::vec3::vec3(r0.y, r1.y, r2.y) * inv,
super::vec3::vec3(r0.z, r1.z, r2.z) * inv,
],
}
}
#[cfg(test)]
pub(crate) fn transpose(&self) -> Mat3 {
Mat3 {
cols: [
super::vec3::vec3(self.cols[0].x, self.cols[1].x, self.cols[2].x),
super::vec3::vec3(self.cols[0].y, self.cols[1].y, self.cols[2].y),
super::vec3::vec3(self.cols[0].z, self.cols[1].z, self.cols[2].z),
],
}
}
}
#[cfg(test)]
mod tests {
use super::super::vec3::vec3;
use super::*;
fn close(a: Vec3, b: Vec3) -> bool {
(a - b).length() < 1.0e-5
}
#[test]
fn identity_and_diagonal_scale_their_axes() {
assert!(close(
Mat3::IDENTITY.mul_vec3(vec3(1.0, 2.0, 3.0)),
vec3(1.0, 2.0, 3.0)
));
let d = Mat3::from_diagonal(vec3(2.0, 3.0, 4.0));
assert!(close(d.mul_vec3(vec3(1.0, 1.0, 1.0)), vec3(2.0, 3.0, 4.0)));
assert_eq!(Mat3::ZERO.mul_vec3(vec3(1.0, 2.0, 3.0)), Vec3::ZERO);
}
#[test]
fn a_sum_of_matrices_adds_columnwise() {
let a = Mat3::from_diagonal(vec3(1.0, 2.0, 3.0));
let b = Mat3::from_cols(Vec3::Y, Vec3::Z, Vec3::X);
let sum = a.add(b);
assert!(close(sum.cols[0], vec3(1.0, 1.0, 0.0)));
assert!(close(sum.cols[1], vec3(0.0, 2.0, 1.0)));
assert!(close(sum.cols[2], vec3(1.0, 0.0, 3.0)));
}
#[test]
fn an_inverse_undoes_the_matrix_it_came_from() {
let rotation = Mat3::from_quat(Quat::from_euler_deg([25.0, -40.0, 65.0]));
for m in [
Mat3::IDENTITY,
Mat3::from_diagonal(vec3(2.0, 0.5, 4.0)),
Mat3::diagonal_conjugated(
Quat::from_euler_deg([10.0, 20.0, 30.0]),
vec3(1.0, 3.0, 7.0),
),
rotation,
] {
let inverse = m.inverse();
for v in [Vec3::X, Vec3::Y, Vec3::Z, vec3(0.4, -1.2, 3.0)] {
assert!(close(inverse.mul_vec3(m.mul_vec3(v)), v), "{m:?} {v:?}");
}
}
}
#[test]
fn a_singular_matrix_inverts_to_zero() {
assert_eq!(Mat3::ZERO.inverse(), Mat3::ZERO);
let flat = Mat3::from_cols(Vec3::X, Vec3::X, Vec3::Z);
assert_eq!(flat.inverse(), Mat3::ZERO);
}
#[test]
fn from_quat_matches_rotating_the_vector_directly() {
let q = Quat::from_euler_deg([15.0, 40.0, -25.0]);
let m = Mat3::from_quat(q);
for v in [Vec3::X, Vec3::Y, Vec3::Z, vec3(1.0, -2.0, 0.5)] {
assert!(close(m.mul_vec3(v), q.rotate(v)), "{v:?}");
}
}
#[test]
fn conjugating_an_isotropic_tensor_leaves_it_alone() {
let q = Quat::from_euler_deg([33.0, -71.0, 12.0]);
let m = Mat3::diagonal_conjugated(q, Vec3::splat(2.5));
for v in [Vec3::X, Vec3::Y, Vec3::Z, vec3(0.3, 0.4, -0.5)] {
assert!(
close(m.mul_vec3(v), v * 2.5),
"{v:?} -> {:?}",
m.mul_vec3(v)
);
}
}
#[test]
fn conjugation_agrees_with_the_three_step_product() {
let q = Quat::from_euler_deg([20.0, 65.0, -40.0]);
let d = vec3(1.0, 4.0, 9.0);
let m = Mat3::diagonal_conjugated(q, d);
for v in [Vec3::X, Vec3::Y, Vec3::Z, vec3(-1.0, 2.0, 3.0)] {
let expected = q.rotate(q.inverse_rotate(v) * d);
assert!(close(m.mul_vec3(v), expected), "{v:?}");
}
}
#[test]
fn a_conjugated_diagonal_is_symmetric() {
let m = Mat3::diagonal_conjugated(
Quat::from_euler_deg([12.0, 34.0, 56.0]),
vec3(1.0, 2.0, 3.0),
);
let t = m.transpose();
for i in 0..3 {
assert!(close(m.cols[i], t.cols[i]), "column {i}");
}
}
}