Skip to main content

voxora_math/
lib.rs

1//! Mathematical primitives and linear algebra for Voxora.
2//!
3//! Provides deterministic vectors, matrices, homogeneous transformations,
4//! and numerical stability utilities.
5
6#![warn(missing_docs)]
7
8pub mod solvers;
9
10pub use solvers::{least_squares_solve, sanitize_float, svd_3x3, SvdResult3x3};
11
12/// Default numerical precision tolerance for floating-point comparisons.
13pub const EPSILON: f64 = 1e-9;
14
15/// 3D Vector representation in continuous spatial coordinates.
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct Vector3 {
18    /// X coordinate
19    pub x: f64,
20    /// Y coordinate
21    pub y: f64,
22    /// Z coordinate
23    pub z: f64,
24}
25
26impl Vector3 {
27    /// Zero vector constant.
28    pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
29
30    /// Creates a new 3D vector.
31    pub fn new(x: f64, y: f64, z: f64) -> Self {
32        Self { x, y, z }
33    }
34
35    /// Computes the dot product between two vectors.
36    pub fn dot(&self, other: &Self) -> f64 {
37        self.x * other.x + self.y * other.y + self.z * other.z
38    }
39
40    /// Computes the cross product between two vectors.
41    pub fn cross(&self, other: &Self) -> Self {
42        Self {
43            x: self.y * other.z - self.z * other.y,
44            y: self.z * other.x - self.x * other.z,
45            z: self.x * other.y - self.y * other.x,
46        }
47    }
48
49    /// Computes the Euclidean norm (magnitude) of the vector.
50    pub fn norm(&self) -> f64 {
51        self.dot(self).sqrt()
52    }
53
54    /// Returns a normalized unit vector, or None if the vector is zero/near-zero.
55    pub fn normalize(&self) -> Option<Self> {
56        let mag = self.norm();
57        if mag < EPSILON {
58            None
59        } else {
60            Some(Self { x: self.x / mag, y: self.y / mag, z: self.z / mag })
61        }
62    }
63}
64
65/// 3x3 Matrix representation in column-major order for camera and projection mathematics.
66#[derive(Debug, Clone, Copy, PartialEq)]
67pub struct Matrix3x3 {
68    /// Elements stored as flat row-major or column-major [f64; 9]
69    pub data: [f64; 9],
70}
71
72impl Matrix3x3 {
73    /// Identity matrix 3x3.
74    pub const IDENTITY: Self = Self { data: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] };
75
76    /// Returns identity matrix.
77    pub fn identity() -> Self {
78        Self::IDENTITY
79    }
80
81    /// Creates a 3x3 matrix from a flat 9-element array in row-major order.
82    pub fn from_row_major(data: [f64; 9]) -> Self {
83        Self { data }
84    }
85
86    /// Returns element at (row, col).
87    pub fn get(&self, row: usize, col: usize) -> f64 {
88        self.data[row * 3 + col]
89    }
90
91    /// Sets element at (row, col).
92    pub fn set(&mut self, row: usize, col: usize, val: f64) {
93        self.data[row * 3 + col] = val;
94    }
95
96    /// Computes the determinant of the 3x3 matrix.
97    pub fn determinant(&self) -> f64 {
98        let m = &self.data;
99        m[0] * (m[4] * m[8] - m[5] * m[7]) - m[1] * (m[3] * m[8] - m[5] * m[6])
100            + m[2] * (m[3] * m[7] - m[4] * m[6])
101    }
102
103    /// Multiplies matrix by a 3D vector.
104    pub fn mul_vec(&self, v: Vector3) -> Vector3 {
105        let m = &self.data;
106        Vector3 {
107            x: m[0] * v.x + m[1] * v.y + m[2] * v.z,
108            y: m[3] * v.x + m[4] * v.y + m[5] * v.z,
109            z: m[6] * v.x + m[7] * v.y + m[8] * v.z,
110        }
111    }
112
113    /// Multiplies two 3x3 matrices together.
114    pub fn mul_mat(&self, other: &Self) -> Self {
115        let mut out = [0.0; 9];
116        for r in 0..3 {
117            for c in 0..3 {
118                let mut sum = 0.0;
119                for k in 0..3 {
120                    sum += self.get(r, k) * other.get(k, c);
121                }
122                out[r * 3 + c] = sum;
123            }
124        }
125        Self::from_row_major(out)
126    }
127
128    /// Computes inverse of 3x3 matrix, or None if singular.
129    pub fn invert(&self) -> Option<Self> {
130        let det = self.determinant();
131        if det.abs() < EPSILON {
132            return None;
133        }
134        let inv_det = 1.0 / det;
135        let m = &self.data;
136
137        let out = [
138            (m[4] * m[8] - m[5] * m[7]) * inv_det,
139            (m[2] * m[7] - m[1] * m[8]) * inv_det,
140            (m[1] * m[5] - m[2] * m[4]) * inv_det,
141            (m[5] * m[6] - m[3] * m[8]) * inv_det,
142            (m[0] * m[8] - m[2] * m[6]) * inv_det,
143            (m[2] * m[3] - m[0] * m[5]) * inv_det,
144            (m[3] * m[7] - m[4] * m[6]) * inv_det,
145            (m[1] * m[6] - m[0] * m[7]) * inv_det,
146            (m[0] * m[4] - m[1] * m[3]) * inv_det,
147        ];
148        Some(Self::from_row_major(out))
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_vector_dot_and_cross() {
158        let v1 = Vector3::new(1.0, 0.0, 0.0);
159        let v2 = Vector3::new(0.0, 1.0, 0.0);
160        assert_eq!(v1.dot(&v2), 0.0);
161
162        let v3 = v1.cross(&v2);
163        assert_eq!(v3, Vector3::new(0.0, 0.0, 1.0));
164    }
165
166    #[test]
167    fn test_vector_normalize() {
168        let v = Vector3::new(3.0, 0.0, 0.0);
169        let norm = v.normalize().unwrap();
170        assert!((norm.x - 1.0).abs() < EPSILON);
171        assert_eq!(norm.y, 0.0);
172        assert_eq!(norm.z, 0.0);
173    }
174
175    #[test]
176    fn test_matrix_determinant_identity() {
177        let mat = Matrix3x3::IDENTITY;
178        assert!((mat.determinant() - 1.0).abs() < EPSILON);
179
180        let v = Vector3::new(2.0, 3.0, 4.0);
181        assert_eq!(mat.mul_vec(v), v);
182    }
183}