#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct Mat3 {
pub m: [[f32; 3]; 3],
}
#[allow(dead_code)]
pub fn mat3_identity() -> Mat3 {
Mat3 {
m: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
}
}
#[allow(dead_code)]
#[allow(clippy::needless_range_loop)]
pub fn mat3_mul(a: &Mat3, b: &Mat3) -> Mat3 {
let mut out = [[0.0f32; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
out[i][j] += a.m[i][k] * b.m[k][j];
}
}
}
Mat3 { m: out }
}
#[allow(dead_code)]
pub fn mat3_det(m: &Mat3) -> f32 {
let a = &m.m;
a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
- a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
+ a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0])
}
#[allow(dead_code)]
pub fn mat3_transpose(m: &Mat3) -> Mat3 {
let a = &m.m;
Mat3 {
m: [
[a[0][0], a[1][0], a[2][0]],
[a[0][1], a[1][1], a[2][1]],
[a[0][2], a[1][2], a[2][2]],
],
}
}
#[allow(dead_code)]
pub fn mat3_transform(m: &Mat3, v: [f32; 3]) -> [f32; 3] {
let a = &m.m;
[
a[0][0] * v[0] + a[0][1] * v[1] + a[0][2] * v[2],
a[1][0] * v[0] + a[1][1] * v[1] + a[1][2] * v[2],
a[2][0] * v[0] + a[2][1] * v[1] + a[2][2] * v[2],
]
}
#[allow(dead_code)]
pub fn mat3_from_scale(sx: f32, sy: f32, sz: f32) -> Mat3 {
Mat3 {
m: [[sx, 0.0, 0.0], [0.0, sy, 0.0], [0.0, 0.0, sz]],
}
}
#[allow(dead_code)]
pub fn mat3_from_rotation_z(angle: f32) -> Mat3 {
let c = angle.cos();
let s = angle.sin();
Mat3 {
m: [[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]],
}
}
#[allow(dead_code)]
pub fn mat3_mul_vec3(a: &Mat3, v: [f32; 3]) -> [f32; 3] {
mat3_transform(a, v)
}
#[allow(dead_code)]
pub fn mat3_scale(s: [f32; 3]) -> Mat3 {
mat3_from_scale(s[0], s[1], s[2])
}
#[allow(dead_code)]
pub fn mat3_from_axis_angle(axis: [f32; 3], angle_rad: f32) -> Mat3 {
let len = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
if len < 1e-9 {
return mat3_identity();
}
let ux = axis[0] / len;
let uy = axis[1] / len;
let uz = axis[2] / len;
let c = angle_rad.cos();
let s = angle_rad.sin();
let t = 1.0 - c;
Mat3 {
m: [
[t * ux * ux + c, t * ux * uy - s * uz, t * ux * uz + s * uy],
[t * ux * uy + s * uz, t * uy * uy + c, t * uy * uz - s * ux],
[t * ux * uz - s * uy, t * uy * uz + s * ux, t * uz * uz + c],
],
}
}
#[allow(dead_code)]
pub fn mat3_inverse(a: &Mat3) -> Option<Mat3> {
let det = mat3_det(a);
if det.abs() < 1e-9 {
return None;
}
let inv = 1.0 / det;
let m = &a.m;
Some(Mat3 {
m: [
[
(m[1][1] * m[2][2] - m[1][2] * m[2][1]) * inv,
(m[0][2] * m[2][1] - m[0][1] * m[2][2]) * inv,
(m[0][1] * m[1][2] - m[0][2] * m[1][1]) * inv,
],
[
(m[1][2] * m[2][0] - m[1][0] * m[2][2]) * inv,
(m[0][0] * m[2][2] - m[0][2] * m[2][0]) * inv,
(m[0][2] * m[1][0] - m[0][0] * m[1][2]) * inv,
],
[
(m[1][0] * m[2][1] - m[1][1] * m[2][0]) * inv,
(m[0][1] * m[2][0] - m[0][0] * m[2][1]) * inv,
(m[0][0] * m[1][1] - m[0][1] * m[1][0]) * inv,
],
],
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_identity_det() {
let id = mat3_identity();
assert!((mat3_det(&id) - 1.0).abs() < 1e-6);
}
#[test]
fn test_identity_transform() {
let id = mat3_identity();
let v = [1.0f32, 2.0, 3.0];
let r = mat3_transform(&id, v);
assert_eq!(r, v);
}
#[test]
fn test_transpose_identity() {
let id = mat3_identity();
let t = mat3_transpose(&id);
assert_eq!(t, id);
}
#[test]
fn test_mul_identity() {
let id = mat3_identity();
let result = mat3_mul(&id, &id);
assert_eq!(result, id);
}
#[test]
fn test_scale_matrix() {
let s = mat3_from_scale(2.0, 3.0, 4.0);
let v = [1.0f32, 1.0, 1.0];
let r = mat3_transform(&s, v);
assert!((r[0] - 2.0).abs() < 1e-6);
assert!((r[1] - 3.0).abs() < 1e-6);
assert!((r[2] - 4.0).abs() < 1e-6);
}
#[test]
fn test_rotation_z_zero() {
let r = mat3_from_rotation_z(0.0);
let v = [1.0f32, 0.0, 0.0];
let rv = mat3_transform(&r, v);
assert!((rv[0] - 1.0).abs() < 1e-6);
assert!(rv[1].abs() < 1e-6);
}
#[test]
fn test_rotation_z_pi() {
let r = mat3_from_rotation_z(PI);
let v = [1.0f32, 0.0, 0.0];
let rv = mat3_transform(&r, v);
assert!((rv[0] + 1.0).abs() < 1e-5);
}
#[test]
fn test_det_scale() {
let s = mat3_from_scale(2.0, 3.0, 4.0);
assert!((mat3_det(&s) - 24.0).abs() < 1e-5);
}
#[test]
fn test_transpose_swap() {
let s = mat3_from_scale(1.0, 2.0, 3.0);
let t = mat3_transpose(&s);
assert!((t.m[0][0] - 1.0).abs() < 1e-6);
assert!((t.m[1][1] - 2.0).abs() < 1e-6);
assert!((t.m[2][2] - 3.0).abs() < 1e-6);
}
#[test]
fn test_mul_scale() {
let s2 = mat3_from_scale(2.0, 2.0, 2.0);
let result = mat3_mul(&s2, &s2);
let v = [1.0f32, 1.0, 1.0];
let rv = mat3_transform(&result, v);
assert!((rv[0] - 4.0).abs() < 1e-5);
}
#[test]
fn test_mul_vec3_alias() {
let id = mat3_identity();
let v = [3.0f32, 4.0, 5.0];
let r = mat3_mul_vec3(&id, v);
assert_eq!(r, v);
}
#[test]
fn test_scale_alias() {
let s = mat3_scale([2.0, 3.0, 4.0]);
let v = [1.0f32, 1.0, 1.0];
let r = mat3_transform(&s, v);
assert!((r[0] - 2.0).abs() < 1e-6);
assert!((r[1] - 3.0).abs() < 1e-6);
assert!((r[2] - 4.0).abs() < 1e-6);
}
#[test]
fn test_from_axis_angle_identity_angle() {
let r = mat3_from_axis_angle([0.0, 0.0, 1.0], 0.0);
let id = mat3_identity();
for i in 0..3 {
for j in 0..3 {
assert!((r.m[i][j] - id.m[i][j]).abs() < 1e-5);
}
}
}
#[test]
fn test_from_axis_angle_z_90() {
use std::f32::consts::FRAC_PI_2;
let r = mat3_from_axis_angle([0.0, 0.0, 1.0], FRAC_PI_2);
let v = mat3_mul_vec3(&r, [1.0, 0.0, 0.0]);
assert!(v[0].abs() < 1e-5);
assert!((v[1] - 1.0).abs() < 1e-5);
}
#[test]
fn test_inverse_identity() {
let id = mat3_identity();
let inv = mat3_inverse(&id).expect("should succeed");
for i in 0..3 {
for j in 0..3 {
assert!((inv.m[i][j] - id.m[i][j]).abs() < 1e-5);
}
}
}
#[test]
fn test_inverse_singular() {
let m = Mat3 {
m: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]],
};
assert!(mat3_inverse(&m).is_none());
}
#[test]
fn test_inverse_roundtrip() {
let s = mat3_from_scale(2.0, 3.0, 4.0);
let inv = mat3_inverse(&s).expect("should succeed");
let prod = mat3_mul(&s, &inv);
let id = mat3_identity();
for i in 0..3 {
for j in 0..3 {
assert!((prod.m[i][j] - id.m[i][j]).abs() < 1e-5);
}
}
}
}