Skip to main content

oxihuman_core/
matrix2.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! 2×2 matrix with determinant, inverse, multiply, and add.
5
6#![allow(dead_code)]
7
8/// A 2×2 matrix stored in row-major order: [[row0col0, row0col1], [row1col0, row1col1]].
9#[allow(dead_code)]
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct Mat2 {
12    pub m: [[f32; 2]; 2],
13}
14
15/// Create a Mat2 from row-major values.
16#[allow(dead_code)]
17pub fn mat2(a: f32, b: f32, c: f32, d: f32) -> Mat2 {
18    Mat2 {
19        m: [[a, b], [c, d]],
20    }
21}
22
23/// Identity matrix.
24#[allow(dead_code)]
25pub fn mat2_identity() -> Mat2 {
26    mat2(1.0, 0.0, 0.0, 1.0)
27}
28
29/// Zero matrix.
30#[allow(dead_code)]
31pub fn mat2_zero() -> Mat2 {
32    mat2(0.0, 0.0, 0.0, 0.0)
33}
34
35/// Determinant of a 2×2 matrix.
36#[allow(dead_code)]
37pub fn mat2_det(m: &Mat2) -> f32 {
38    m.m[0][0] * m.m[1][1] - m.m[0][1] * m.m[1][0]
39}
40
41/// Inverse of a 2×2 matrix. Returns None if singular.
42#[allow(dead_code)]
43pub fn mat2_inverse(m: &Mat2) -> Option<Mat2> {
44    let det = mat2_det(m);
45    if det.abs() < 1e-10 {
46        return None;
47    }
48    let inv_det = 1.0 / det;
49    Some(mat2(
50        m.m[1][1] * inv_det,
51        -m.m[0][1] * inv_det,
52        -m.m[1][0] * inv_det,
53        m.m[0][0] * inv_det,
54    ))
55}
56
57/// Transpose of a 2×2 matrix.
58#[allow(dead_code)]
59pub fn mat2_transpose(m: &Mat2) -> Mat2 {
60    mat2(m.m[0][0], m.m[1][0], m.m[0][1], m.m[1][1])
61}
62
63/// Multiply two 2×2 matrices: result = a * b.
64#[allow(dead_code)]
65pub fn mat2_mul(a: &Mat2, b: &Mat2) -> Mat2 {
66    mat2(
67        a.m[0][0] * b.m[0][0] + a.m[0][1] * b.m[1][0],
68        a.m[0][0] * b.m[0][1] + a.m[0][1] * b.m[1][1],
69        a.m[1][0] * b.m[0][0] + a.m[1][1] * b.m[1][0],
70        a.m[1][0] * b.m[0][1] + a.m[1][1] * b.m[1][1],
71    )
72}
73
74/// Add two 2×2 matrices.
75#[allow(dead_code)]
76pub fn mat2_add(a: &Mat2, b: &Mat2) -> Mat2 {
77    mat2(
78        a.m[0][0] + b.m[0][0],
79        a.m[0][1] + b.m[0][1],
80        a.m[1][0] + b.m[1][0],
81        a.m[1][1] + b.m[1][1],
82    )
83}
84
85/// Scale a 2×2 matrix by a scalar.
86#[allow(dead_code)]
87pub fn mat2_scale(m: &Mat2, s: f32) -> Mat2 {
88    mat2(m.m[0][0] * s, m.m[0][1] * s, m.m[1][0] * s, m.m[1][1] * s)
89}
90
91/// Multiply a 2×2 matrix by a 2D column vector.
92#[allow(dead_code)]
93pub fn mat2_mul_vec(m: &Mat2, v: [f32; 2]) -> [f32; 2] {
94    [
95        m.m[0][0] * v[0] + m.m[0][1] * v[1],
96        m.m[1][0] * v[0] + m.m[1][1] * v[1],
97    ]
98}
99
100/// Create a 2D rotation matrix for angle `theta` (radians).
101#[allow(dead_code)]
102pub fn mat2_rotation(theta: f32) -> Mat2 {
103    let c = theta.cos();
104    let s = theta.sin();
105    mat2(c, -s, s, c)
106}
107
108/// Trace of the matrix (sum of diagonal elements).
109#[allow(dead_code)]
110pub fn mat2_trace(m: &Mat2) -> f32 {
111    m.m[0][0] + m.m[1][1]
112}
113
114/// Check if two matrices are approximately equal within `eps`.
115#[allow(dead_code)]
116pub fn mat2_approx_eq(a: &Mat2, b: &Mat2, eps: f32) -> bool {
117    (a.m[0][0] - b.m[0][0]).abs() < eps
118        && (a.m[0][1] - b.m[0][1]).abs() < eps
119        && (a.m[1][0] - b.m[1][0]).abs() < eps
120        && (a.m[1][1] - b.m[1][1]).abs() < eps
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use std::f32::consts::PI;
127
128    #[test]
129    fn test_identity_det() {
130        let id = mat2_identity();
131        assert!((mat2_det(&id) - 1.0).abs() < 1e-5);
132    }
133
134    #[test]
135    fn test_zero_det() {
136        let z = mat2_zero();
137        assert_eq!(mat2_det(&z), 0.0);
138    }
139
140    #[test]
141    fn test_inverse() {
142        let m = mat2(2.0, 1.0, 5.0, 3.0);
143        let inv = mat2_inverse(&m).expect("should succeed");
144        let prod = mat2_mul(&m, &inv);
145        assert!(mat2_approx_eq(&prod, &mat2_identity(), 1e-5));
146    }
147
148    #[test]
149    fn test_singular_inverse() {
150        let m = mat2(1.0, 2.0, 2.0, 4.0);
151        assert!(mat2_inverse(&m).is_none());
152    }
153
154    #[test]
155    fn test_mul_identity() {
156        let m = mat2(3.0, 7.0, 2.0, 5.0);
157        let id = mat2_identity();
158        let result = mat2_mul(&m, &id);
159        assert!(mat2_approx_eq(&result, &m, 1e-5));
160    }
161
162    #[test]
163    fn test_transpose() {
164        let m = mat2(1.0, 2.0, 3.0, 4.0);
165        let t = mat2_transpose(&m);
166        assert!((t.m[0][1] - 3.0).abs() < 1e-5);
167        assert!((t.m[1][0] - 2.0).abs() < 1e-5);
168    }
169
170    #[test]
171    fn test_mul_vec() {
172        let m = mat2(1.0, 0.0, 0.0, 2.0);
173        let v = mat2_mul_vec(&m, [3.0, 4.0]);
174        assert!((v[0] - 3.0).abs() < 1e-5);
175        assert!((v[1] - 8.0).abs() < 1e-5);
176    }
177
178    #[test]
179    fn test_rotation_matrix() {
180        let r = mat2_rotation(PI / 2.0);
181        let v = mat2_mul_vec(&r, [1.0, 0.0]);
182        assert!(v[0].abs() < 1e-5);
183        assert!((v[1] - 1.0).abs() < 1e-5);
184    }
185
186    #[test]
187    fn test_trace() {
188        let m = mat2(3.0, 1.0, 2.0, 5.0);
189        assert!((mat2_trace(&m) - 8.0).abs() < 1e-5);
190    }
191
192    #[test]
193    fn test_scale() {
194        let m = mat2_identity();
195        let s = mat2_scale(&m, 3.0);
196        assert!((s.m[0][0] - 3.0).abs() < 1e-5);
197        assert!((s.m[1][1] - 3.0).abs() < 1e-5);
198    }
199}