1use glam::Vec3Swizzles;
10use num_traits::float::FloatConst;
11
12#[cfg(not(feature = "std"))]
13use simba::scalar::ComplexField;
14
15macro_rules! impl_symmetric_eigen3 {
17 ($SymmetricEigen3:ident, $Mat3:ty, $Vec3:ty, $Real:ty $(, #[$attr:meta])*) => {
18 #[doc = concat!("The eigen decomposition of a symmetric 3x3 matrix (", stringify!($Real), " precision).")]
19 #[derive(Clone, Copy, Debug, PartialEq)]
20 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21 $(#[$attr])*
22 pub struct $SymmetricEigen3 {
23 pub eigenvalues: $Vec3,
25 pub eigenvectors: $Mat3,
27 }
28
29 impl $SymmetricEigen3 {
30 pub fn new(mat: $Mat3) -> Self {
35 let eigenvalues = Self::eigenvalues(mat);
36 let eigenvector1 = Self::eigenvector1(mat, eigenvalues.x);
37 let eigenvector2 = Self::eigenvector2(mat, eigenvector1, eigenvalues.y);
38 let eigenvector3 = Self::eigenvector3(eigenvector1, eigenvector2);
39
40 Self {
41 eigenvalues,
42 eigenvectors: <$Mat3>::from_cols(eigenvector1, eigenvector2, eigenvector3),
43 }
44 }
45
46 pub fn reverse(&self) -> Self {
48 Self {
49 eigenvalues: self.eigenvalues.zyx(),
50 eigenvectors: <$Mat3>::from_cols(
51 self.eigenvectors.z_axis,
52 self.eigenvectors.y_axis,
53 self.eigenvectors.x_axis,
54 ),
55 }
56 }
57
58 pub fn eigenvalues(mat: $Mat3) -> $Vec3 {
62 let p1 = mat.y_axis.x.powi(2) + mat.z_axis.x.powi(2) + mat.z_axis.y.powi(2);
63 if p1 == 0.0 {
64 let mut eigenvalues = [mat.x_axis.x, mat.y_axis.y, mat.z_axis.z];
66 eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
67 <$Vec3>::from_array(eigenvalues)
68 } else {
69 let q = (mat.x_axis.x + mat.y_axis.y + mat.z_axis.z) / 3.0;
70 let p2 = (mat.x_axis.x - q).powi(2)
71 + (mat.y_axis.y - q).powi(2)
72 + (mat.z_axis.z - q).powi(2)
73 + 2.0 * p1;
74 let p = (p2 / 6.0).sqrt();
75 let mat_b = 1.0 / p * (mat - q * <$Mat3>::IDENTITY);
76 let r = mat_b.determinant() / 2.0;
77
78 let pi: $Real = <$Real as FloatConst>::PI();
81 let phi = if r <= -1.0 {
82 pi / 3.0
83 } else if r >= 1.0 {
84 0.0
85 } else {
86 r.acos() / 3.0
87 };
88
89 let eigen1 = q + 2.0 * p * phi.cos();
91 let eigen3 = q + 2.0 * p * (phi + 2.0 * pi / 3.0).cos();
92 let eigen2 = 3.0 * q - eigen1 - eigen3; <$Vec3>::new(eigen3, eigen2, eigen1)
94 }
95 }
96
97 pub fn eigenvector1(mat: $Mat3, eigenvalue1: $Real) -> $Vec3 {
106 let cols = mat - <$Mat3>::from_diagonal(<$Vec3>::splat(eigenvalue1).into());
107 let c0xc1 = cols.x_axis.cross(cols.y_axis);
108 let c0xc2 = cols.x_axis.cross(cols.z_axis);
109 let c1xc2 = cols.y_axis.cross(cols.z_axis);
110 let d0 = c0xc1.length_squared();
111 let d1 = c0xc2.length_squared();
112 let d2 = c1xc2.length_squared();
113
114 let mut d_max = d0;
115 let mut i_max = 0;
116
117 if d1 > d_max {
118 d_max = d1;
119 i_max = 1;
120 }
121 if d2 > d_max {
122 d_max = d2;
123 i_max = 2;
124 }
125
126 if d_max < 1e-20 {
130 return <$Vec3>::X;
131 }
132
133 if i_max == 0 {
134 c0xc1 / d0.sqrt()
135 } else if i_max == 1 {
136 c0xc2 / d1.sqrt()
137 } else {
138 c1xc2 / d2.sqrt()
139 }
140 }
141
142 pub fn eigenvector2(mat: $Mat3, eigenvector1: $Vec3, eigenvalue2: $Real) -> $Vec3 {
147 let (u, v) = Self::any_orthonormal_pair(eigenvector1);
149
150 let au = mat * u;
161 let av = mat * v;
162
163 let mut m00 = u.dot(au) - eigenvalue2;
164 let mut m01 = u.dot(av);
165 let mut m11 = v.dot(av) - eigenvalue2;
166 let (abs_m00, abs_m01, abs_m11) = (m00.abs(), m01.abs(), m11.abs());
167
168 if abs_m00 >= abs_m11 {
169 let max_abs_component = abs_m00.max(abs_m01);
170 if max_abs_component > 0.0 {
171 if abs_m00 >= abs_m01 {
172 m01 /= m00;
175 m00 = 1.0 / (1.0 + m01 * m01).sqrt();
176 m01 *= m00;
177 } else {
178 m00 /= m01;
181 m01 = 1.0 / (1.0 + m00 * m00).sqrt();
182 m00 *= m01;
183 }
184 return m01 * u - m00 * v;
185 }
186 } else {
187 let max_abs_component = abs_m11.max(abs_m01);
188 if max_abs_component > 0.0 {
189 if abs_m11 >= abs_m01 {
190 m01 /= m11;
193 m11 = 1.0 / (1.0 + m01 * m01).sqrt();
194 m01 *= m11;
195 } else {
196 m11 /= m01;
199 m01 = 1.0 / (1.0 + m11 * m11).sqrt();
200 m11 *= m01;
201 }
202 return m11 * u - m01 * v;
203 }
204 }
205
206 u
208 }
209
210 pub fn eigenvector3(eigenvector1: $Vec3, eigenvector2: $Vec3) -> $Vec3 {
213 eigenvector1.cross(eigenvector2)
214 }
215
216 fn any_orthonormal_pair(v: $Vec3) -> ($Vec3, $Vec3) {
218 let sign = if v.z >= 0.0 { 1.0 } else { -1.0 };
219 let a = -1.0 / (sign + v.z);
220 let b = v.x * v.y * a;
221 let u = <$Vec3>::new(1.0 + sign * v.x * v.x * a, sign * b, -sign * v.x);
222 let w = <$Vec3>::new(b, sign + v.y * v.y * a, -v.y);
223 (u, w)
224 }
225 }
226 };
227}
228
229impl_symmetric_eigen3!(SymmetricEigen3, glam::Mat3, glam::Vec3, f32);
230impl_symmetric_eigen3!(SymmetricEigen3A, glam::Mat3A, glam::Vec3A, f32);
231impl_symmetric_eigen3!(DSymmetricEigen3, glam::DMat3, glam::DVec3, f64);
232
233impl From<SymmetricEigen3> for DSymmetricEigen3 {
235 #[inline]
236 fn from(e: SymmetricEigen3) -> Self {
237 Self {
238 eigenvalues: e.eigenvalues.as_dvec3(),
239 eigenvectors: e.eigenvectors.as_dmat3(),
240 }
241 }
242}
243
244impl From<DSymmetricEigen3> for SymmetricEigen3 {
245 #[inline]
246 fn from(e: DSymmetricEigen3) -> Self {
247 Self {
248 eigenvalues: e.eigenvalues.as_vec3(),
249 eigenvectors: e.eigenvectors.as_mat3(),
250 }
251 }
252}
253
254impl From<SymmetricEigen3A> for DSymmetricEigen3 {
255 #[inline]
256 fn from(e: SymmetricEigen3A) -> Self {
257 Self {
258 eigenvalues: e.eigenvalues.as_dvec3(),
259 eigenvectors: e.eigenvectors.as_dmat3(),
260 }
261 }
262}
263
264impl From<DSymmetricEigen3> for SymmetricEigen3A {
265 #[inline]
266 fn from(e: DSymmetricEigen3) -> Self {
267 Self {
268 eigenvalues: e.eigenvalues.as_vec3a(),
269 eigenvectors: e.eigenvectors.as_mat3().into(),
270 }
271 }
272}
273
274impl From<SymmetricEigen3> for SymmetricEigen3A {
276 #[inline]
277 fn from(e: SymmetricEigen3) -> Self {
278 Self {
279 eigenvalues: e.eigenvalues.into(),
280 eigenvectors: glam::Mat3A::from(e.eigenvectors),
281 }
282 }
283}
284
285impl From<SymmetricEigen3A> for SymmetricEigen3 {
286 #[inline]
287 fn from(e: SymmetricEigen3A) -> Self {
288 Self {
289 eigenvalues: e.eigenvalues.into(),
290 eigenvectors: glam::Mat3::from(e.eigenvectors),
291 }
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use approx::assert_relative_eq;
299
300 #[test]
301 fn eigen_3x3() {
302 let mat =
303 glam::Mat3::from_cols_array_2d(&[[2.0, 7.0, 8.0], [7.0, 6.0, 3.0], [8.0, 3.0, 0.0]]);
304 let eigen = SymmetricEigen3::new(mat);
305
306 assert_relative_eq!(
307 eigen.eigenvalues,
308 glam::Vec3::new(-7.6051016780, 0.5774961930, 15.0276054850),
309 epsilon = 1.0e-6
310 );
311 assert_relative_eq!(
312 glam::Mat3::from_cols(
313 eigen.eigenvectors.x_axis,
314 eigen.eigenvectors.y_axis,
315 eigen.eigenvectors.z_axis
316 ),
317 glam::Mat3::from_cols(
318 glam::Vec3::new(1.0754474819, -0.3328260589, -1.0).normalize(),
319 glam::Vec3::new(-0.5420669703, 1.2530131898, -1.0).normalize(),
320 glam::Vec3::new(1.3587443369, 1.3858835966, 1.0).normalize()
321 ),
322 epsilon = 1.0e-6
323 );
324 }
325
326 #[test]
327 fn eigen_3x3_diagonal() {
328 let mat =
329 glam::Mat3::from_cols_array_2d(&[[2.0, 0.0, 0.0], [0.0, 5.0, 0.0], [0.0, 0.0, 3.0]]);
330 let eigen = SymmetricEigen3::new(mat);
331
332 assert_eq!(eigen.eigenvalues, glam::Vec3::new(2.0, 3.0, 5.0));
333 assert_eq!(
334 glam::Mat3::from_cols(
335 eigen.eigenvectors.x_axis.normalize().abs(),
336 eigen.eigenvectors.y_axis.normalize().abs(),
337 eigen.eigenvectors.z_axis.normalize().abs()
338 ),
339 glam::Mat3::from_cols_array_2d(&[[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]])
340 );
341 }
342
343 #[test]
344 fn eigen_3x3_f64() {
345 let mat =
346 glam::DMat3::from_cols_array_2d(&[[2.0, 7.0, 8.0], [7.0, 6.0, 3.0], [8.0, 3.0, 0.0]]);
347 let eigen = DSymmetricEigen3::new(mat);
348
349 assert_relative_eq!(
350 eigen.eigenvalues,
351 glam::DVec3::new(-7.6051016780, 0.5774961930, 15.0276054850),
352 epsilon = 1.0e-8
353 );
354 }
355
356 #[test]
357 fn eigen_3x3_identity() {
358 let mat = glam::Mat3::IDENTITY;
361 let eigen = SymmetricEigen3::new(mat);
362
363 assert_eq!(eigen.eigenvalues, glam::Vec3::ONE);
364
365 assert_relative_eq!(
367 eigen.eigenvectors * eigen.eigenvectors.transpose(),
368 glam::Mat3::IDENTITY,
369 epsilon = 1e-6
370 );
371 }
372
373 #[test]
374 fn eigen_3x3_uniform_scaling() {
375 let mat = glam::Mat3::from_diagonal(glam::Vec3::splat(5.0));
377 let eigen = SymmetricEigen3::new(mat);
378
379 assert_eq!(eigen.eigenvalues, glam::Vec3::splat(5.0));
380
381 assert_relative_eq!(
383 eigen.eigenvectors * eigen.eigenvectors.transpose(),
384 glam::Mat3::IDENTITY,
385 epsilon = 1e-6
386 );
387 }
388}