glamx/
matrix_ext.rs

1//! Matrix extension traits for glam types.
2
3use crate::eigen2::{DSymmetricEigen2, SymmetricEigen2};
4use crate::eigen3::{DSymmetricEigen3, SymmetricEigen3, SymmetricEigen3A};
5use crate::svd2::{DSvd2, Svd2};
6use crate::svd3::{DSvd3, Svd3, Svd3A};
7
8/// Extension trait for square matrix types.
9///
10/// Provides additional functionality not available in glam:
11/// - `abs()` - element-wise absolute value
12/// - `try_inverse()` - inverse with singularity check
13/// - `swap_cols()` / `swap_rows()` - column/row swapping
14/// - `symmetric_eigen()` - eigendecomposition for symmetric matrices
15pub trait MatExt: Sized + Copy {
16    /// The scalar type.
17    type Scalar;
18    /// The vector type.
19    type Vector;
20    /// The symmetric eigen decomposition type.
21    type SymmetricEigen;
22    /// The Singular Values Decomposition type.
23    type Svd;
24
25    /// Returns a matrix with all components set to their absolute values.
26    fn abs(&self) -> Self;
27
28    /// Tries to invert the matrix, returning None if not invertible.
29    fn try_inverse(&self) -> Option<Self>;
30
31    /// Swaps two columns of this matrix.
32    fn swap_cols(&mut self, a: usize, b: usize);
33
34    /// Swaps two rows of this matrix.
35    fn swap_rows(&mut self, a: usize, b: usize);
36
37    /// Computes the symmetric eigendecomposition.
38    ///
39    /// If `self` isn’t symmetric, expect incorrect results.
40    fn symmetric_eigen(&self) -> Self::SymmetricEigen;
41
42    /// Computes the eigenvalues of a symmetric matrix.
43    ///
44    /// If `self` isn’t symmetric, expect incorrect results.
45    fn symmetric_eigenvalues(&self) -> Self::Vector;
46
47    /// Computes the SVD of this matrix.
48    fn svd(&self) -> Self::Svd;
49}
50
51/// Macro to implement MatExt for a specific matrix type.
52macro_rules! impl_mat2_ext {
53    ($Mat2:ty, $Vec2:ty, $Real:ty, $SymmetricEigen2:ty, $Svd2:ty) => {
54        impl MatExt for $Mat2 {
55            type Scalar = $Real;
56            type Vector = $Vec2;
57            type SymmetricEigen = $SymmetricEigen2;
58            type Svd = $Svd2;
59
60            #[inline]
61            fn abs(&self) -> Self {
62                Self::from_cols(self.x_axis.abs(), self.y_axis.abs())
63            }
64
65            #[inline]
66            fn try_inverse(&self) -> Option<Self> {
67                let det = self.determinant();
68                if det.abs() < <$Real>::EPSILON {
69                    None
70                } else {
71                    Some(self.inverse())
72                }
73            }
74
75            #[inline]
76            fn swap_cols(&mut self, a: usize, b: usize) {
77                assert!(a < 2, "column index {a} is out of bounds");
78                assert!(b < 2, "column index {b} is out of bounds");
79
80                let ca = self.col(a);
81                let cb = self.col(b);
82                *self.col_mut(a) = cb;
83                *self.col_mut(b) = ca;
84            }
85
86            #[inline]
87            fn swap_rows(&mut self, a: usize, b: usize) {
88                assert!(a < 2, "row index {a} is out of bounds");
89                assert!(b < 2, "row index {b} is out of bounds");
90                self.x_axis.as_mut().swap(a, b);
91                self.y_axis.as_mut().swap(a, b);
92            }
93
94            #[inline]
95            fn symmetric_eigen(&self) -> Self::SymmetricEigen {
96                <$SymmetricEigen2>::new(*self)
97            }
98
99            #[inline]
100            fn symmetric_eigenvalues(&self) -> Self::Vector {
101                <$SymmetricEigen2>::eigenvalues(*self)
102            }
103
104            #[inline]
105            fn svd(&self) -> Self::Svd {
106                <$Svd2>::from_matrix(*self)
107            }
108        }
109    };
110}
111
112/// Macro to implement MatExt for a specific matrix type.
113macro_rules! impl_mat3_ext {
114    ($Mat3:ty, $Vec3:ty, $Real:ty, $SymmetricEigen3:ty, $Svd3:ty) => {
115        impl MatExt for $Mat3 {
116            type Scalar = $Real;
117            type Vector = $Vec3;
118            type SymmetricEigen = $SymmetricEigen3;
119            type Svd = $Svd3;
120
121            #[inline]
122            fn abs(&self) -> Self {
123                Self::from_cols(self.x_axis.abs(), self.y_axis.abs(), self.z_axis.abs())
124            }
125
126            #[inline]
127            fn try_inverse(&self) -> Option<Self> {
128                let det = self.determinant();
129                if det.abs() < <$Real>::EPSILON {
130                    None
131                } else {
132                    Some(self.inverse())
133                }
134            }
135
136            fn swap_cols(&mut self, a: usize, b: usize) {
137                assert!(a < 3, "column index {a} is out of bounds");
138                assert!(b < 3, "column index {b} is out of bounds");
139
140                let ca = self.col(a);
141                let cb = self.col(b);
142                *self.col_mut(a) = cb;
143                *self.col_mut(b) = ca;
144            }
145
146            fn swap_rows(&mut self, a: usize, b: usize) {
147                assert!(a < 3, "row index {a} is out of bounds");
148                assert!(b < 3, "row index {b} is out of bounds");
149                self.x_axis.as_mut().swap(a, b);
150                self.y_axis.as_mut().swap(a, b);
151                self.z_axis.as_mut().swap(a, b);
152            }
153
154            fn symmetric_eigen(&self) -> Self::SymmetricEigen {
155                <$SymmetricEigen3>::new(*self)
156            }
157
158            fn symmetric_eigenvalues(&self) -> Self::Vector {
159                <$SymmetricEigen3>::eigenvalues(*self)
160            }
161
162            #[inline]
163            fn svd(&self) -> Self::Svd {
164                <$Svd3>::from_matrix(*self)
165            }
166        }
167    };
168}
169
170impl_mat2_ext!(glam::Mat2, glam::Vec2, f32, SymmetricEigen2, Svd2);
171impl_mat2_ext!(glam::DMat2, glam::DVec2, f64, DSymmetricEigen2, DSvd2);
172impl_mat3_ext!(glam::Mat3, glam::Vec3, f32, SymmetricEigen3, Svd3);
173impl_mat3_ext!(glam::Mat3A, glam::Vec3A, f32, SymmetricEigen3A, Svd3A);
174impl_mat3_ext!(glam::DMat3, glam::DVec3, f64, DSymmetricEigen3, DSvd3);
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_mat2_abs() {
182        let m = glam::Mat2::from_cols(glam::Vec2::new(-1.0, 2.0), glam::Vec2::new(3.0, -4.0));
183        let abs_m = m.abs();
184        assert_eq!(abs_m.x_axis, glam::Vec2::new(1.0, 2.0));
185        assert_eq!(abs_m.y_axis, glam::Vec2::new(3.0, 4.0));
186    }
187
188    #[test]
189    fn test_mat2_try_inverse() {
190        let m = glam::Mat2::from_cols(glam::Vec2::new(1.0, 2.0), glam::Vec2::new(3.0, 4.0));
191        let inv = m.try_inverse();
192        assert!(inv.is_some());
193
194        // Singular matrix
195        let singular = glam::Mat2::from_cols(glam::Vec2::new(1.0, 2.0), glam::Vec2::new(2.0, 4.0));
196        let no_inv = singular.try_inverse();
197        assert!(no_inv.is_none());
198    }
199
200    #[test]
201    fn test_mat3_abs() {
202        let m = glam::Mat3::from_cols(
203            glam::Vec3::new(-1.0, 2.0, -3.0),
204            glam::Vec3::new(4.0, -5.0, 6.0),
205            glam::Vec3::new(-7.0, 8.0, -9.0),
206        );
207        let abs_m = m.abs();
208        assert_eq!(abs_m.x_axis, glam::Vec3::new(1.0, 2.0, 3.0));
209        assert_eq!(abs_m.y_axis, glam::Vec3::new(4.0, 5.0, 6.0));
210        assert_eq!(abs_m.z_axis, glam::Vec3::new(7.0, 8.0, 9.0));
211    }
212
213    #[test]
214    fn test_mat3_swap_cols() {
215        let mut m = glam::Mat3::from_cols(
216            glam::Vec3::new(1.0, 2.0, 3.0),
217            glam::Vec3::new(4.0, 5.0, 6.0),
218            glam::Vec3::new(7.0, 8.0, 9.0),
219        );
220        m.swap_cols(0, 2);
221        assert_eq!(m.x_axis, glam::Vec3::new(7.0, 8.0, 9.0));
222        assert_eq!(m.z_axis, glam::Vec3::new(1.0, 2.0, 3.0));
223    }
224
225    #[test]
226    fn test_dmat2_abs() {
227        let m = glam::DMat2::from_cols(glam::DVec2::new(-1.0, 2.0), glam::DVec2::new(3.0, -4.0));
228        let abs_m = m.abs();
229        assert_eq!(abs_m.x_axis, glam::DVec2::new(1.0, 2.0));
230        assert_eq!(abs_m.y_axis, glam::DVec2::new(3.0, 4.0));
231    }
232
233    #[test]
234    fn test_mat3_symmetric_eigen() {
235        use approx::assert_relative_eq;
236        let m =
237            glam::Mat3::from_cols_array_2d(&[[2.0, 0.0, 0.0], [0.0, 3.0, 0.0], [0.0, 0.0, 5.0]]);
238        let eigenvalues = m.symmetric_eigenvalues();
239        // Eigenvalues of a diagonal matrix are its diagonal entries, sorted ascending
240        assert_relative_eq!(eigenvalues.x, 2.0, epsilon = 0.001);
241        assert_relative_eq!(eigenvalues.y, 3.0, epsilon = 0.001);
242        assert_relative_eq!(eigenvalues.z, 5.0, epsilon = 0.001);
243    }
244}