Skip to main content

glamx/
matrix_ext.rs

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