1#![allow(clippy::manual_swap)] use glam::Vec3Swizzles;
12use num_traits::float::FloatConst;
13
14#[cfg(not(feature = "std"))]
15use simba::scalar::ComplexField;
16
17macro_rules! impl_symmetric_eigen3 {
19 ($SymmetricEigen3:ident, $Mat3:ty, $Vec3:ty, $Real:ty $(, #[$attr:meta])*) => {
20 #[doc = concat!("The eigen decomposition of a symmetric 3x3 matrix (", stringify!($Real), " precision).")]
21 #[derive(Clone, Copy, Debug, PartialEq)]
22 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23 $(#[$attr])*
24 pub struct $SymmetricEigen3 {
25 pub eigenvalues: $Vec3,
27 pub eigenvectors: $Mat3,
29 }
30
31 impl $SymmetricEigen3 {
32 pub fn new(mat: $Mat3) -> Self {
37 let eigenvalues = Self::eigenvalues(mat);
38 let eigenvector1 = Self::eigenvector1(mat, eigenvalues.x);
39 let eigenvector2 = Self::eigenvector2(mat, eigenvector1, eigenvalues.y);
40 let eigenvector3 = Self::eigenvector3(eigenvector1, eigenvector2);
41
42 Self {
43 eigenvalues,
44 eigenvectors: <$Mat3>::from_cols(eigenvector1, eigenvector2, eigenvector3),
45 }
46 }
47
48 pub fn reverse(&self) -> Self {
50 Self {
51 eigenvalues: self.eigenvalues.zyx(),
52 eigenvectors: <$Mat3>::from_cols(
53 self.eigenvectors.z_axis,
54 self.eigenvectors.y_axis,
55 self.eigenvectors.x_axis,
56 ),
57 }
58 }
59
60 pub fn eigenvalues(mat: $Mat3) -> $Vec3 {
64 let p1 = mat.y_axis.x.powi(2) + mat.z_axis.x.powi(2) + mat.z_axis.y.powi(2);
65 if p1 == 0.0 {
66 let mut eigenvalues = [mat.x_axis.x, mat.y_axis.y, mat.z_axis.z];
68 if eigenvalues[0] > eigenvalues[1] { let t = eigenvalues[0]; eigenvalues[0] = eigenvalues[1]; eigenvalues[1] = t; }
72 if eigenvalues[1] > eigenvalues[2] { let t = eigenvalues[1]; eigenvalues[1] = eigenvalues[2]; eigenvalues[2] = t; }
73 if eigenvalues[0] > eigenvalues[1] { let t = eigenvalues[0]; eigenvalues[0] = eigenvalues[1]; eigenvalues[1] = t; }
74 <$Vec3>::from_array(eigenvalues)
75 } else {
76 let q = (mat.x_axis.x + mat.y_axis.y + mat.z_axis.z) / 3.0;
77 let p2 = (mat.x_axis.x - q).powi(2)
78 + (mat.y_axis.y - q).powi(2)
79 + (mat.z_axis.z - q).powi(2)
80 + 2.0 * p1;
81
82 let p = (p2 / 6.0).sqrt();
83
84 let r = if p != 0.0 {
87 let mat_b = 1.0 / p * (mat - q * <$Mat3>::IDENTITY);
88 mat_b.determinant() / 2.0
89 } else {
90 1.0 };
92
93 let pi: $Real = <$Real as FloatConst>::PI();
96 let phi = if r <= -1.0 {
97 pi / 3.0
98 } else if r >= 1.0 {
99 0.0
100 } else {
101 r.acos() / 3.0
102 };
103
104 let eigen1 = q + 2.0 * p * phi.cos();
106 let eigen3 = q + 2.0 * p * (phi + 2.0 * pi / 3.0).cos();
107 let eigen2 = 3.0 * q - eigen1 - eigen3; <$Vec3>::new(eigen3, eigen2, eigen1)
109 }
110 }
111
112 pub fn eigenvector1(mat: $Mat3, eigenvalue1: $Real) -> $Vec3 {
121 let cols = mat - <$Mat3>::from_diagonal(<$Vec3>::splat(eigenvalue1).into());
122 let c0xc1 = cols.x_axis.cross(cols.y_axis);
123 let c0xc2 = cols.x_axis.cross(cols.z_axis);
124 let c1xc2 = cols.y_axis.cross(cols.z_axis);
125 let d0 = c0xc1.length_squared();
126 let d1 = c0xc2.length_squared();
127 let d2 = c1xc2.length_squared();
128
129 let mut d_max = d0;
130 let mut i_max = 0;
131
132 if d1 > d_max {
133 d_max = d1;
134 i_max = 1;
135 }
136 if d2 > d_max {
137 d_max = d2;
138 i_max = 2;
139 }
140
141 if d_max < 1e-20 {
145 return <$Vec3>::X;
146 }
147
148 if i_max == 0 {
149 c0xc1 / d0.sqrt()
150 } else if i_max == 1 {
151 c0xc2 / d1.sqrt()
152 } else {
153 c1xc2 / d2.sqrt()
154 }
155 }
156
157 pub fn eigenvector2(mat: $Mat3, eigenvector1: $Vec3, eigenvalue2: $Real) -> $Vec3 {
162 let (u, v) = Self::any_orthonormal_pair(eigenvector1);
164
165 let au = mat * u;
176 let av = mat * v;
177
178 let mut m00 = u.dot(au) - eigenvalue2;
179 let mut m01 = u.dot(av);
180 let mut m11 = v.dot(av) - eigenvalue2;
181 let (abs_m00, abs_m01, abs_m11) = (m00.abs(), m01.abs(), m11.abs());
182
183 if abs_m00 >= abs_m11 {
184 let max_abs_component = abs_m00.max(abs_m01);
185 if max_abs_component > 0.0 {
186 if abs_m00 >= abs_m01 {
187 m01 /= m00;
190 m00 = 1.0 / (1.0 + m01 * m01).sqrt();
191 m01 *= m00;
192 } else {
193 m00 /= m01;
196 m01 = 1.0 / (1.0 + m00 * m00).sqrt();
197 m00 *= m01;
198 }
199 return m01 * u - m00 * v;
200 }
201 } else {
202 let max_abs_component = abs_m11.max(abs_m01);
203 if max_abs_component > 0.0 {
204 if abs_m11 >= abs_m01 {
205 m01 /= m11;
208 m11 = 1.0 / (1.0 + m01 * m01).sqrt();
209 m01 *= m11;
210 } else {
211 m11 /= m01;
214 m01 = 1.0 / (1.0 + m11 * m11).sqrt();
215 m11 *= m01;
216 }
217 return m11 * u - m01 * v;
218 }
219 }
220
221 u
223 }
224
225 pub fn eigenvector3(eigenvector1: $Vec3, eigenvector2: $Vec3) -> $Vec3 {
228 eigenvector1.cross(eigenvector2)
229 }
230
231 fn any_orthonormal_pair(v: $Vec3) -> ($Vec3, $Vec3) {
233 let sign = if v.z >= 0.0 { 1.0 } else { -1.0 };
234 let a = -1.0 / (sign + v.z);
235 let b = v.x * v.y * a;
236 let u = <$Vec3>::new(1.0 + sign * v.x * v.x * a, sign * b, -sign * v.x);
237 let w = <$Vec3>::new(b, sign + v.y * v.y * a, -v.y);
238 (u, w)
239 }
240 }
241 };
242}
243
244impl_symmetric_eigen3!(SymmetricEigen3, glam::Mat3, glam::Vec3, f32);
245impl_symmetric_eigen3!(SymmetricEigen3A, glam::Mat3A, glam::Vec3A, f32);
246#[cfg(feature = "f64")]
247impl_symmetric_eigen3!(DSymmetricEigen3, glam::DMat3, glam::DVec3, f64);
248
249#[cfg(feature = "f64")]
251impl From<SymmetricEigen3> for DSymmetricEigen3 {
252 #[inline]
253 fn from(e: SymmetricEigen3) -> Self {
254 Self {
255 eigenvalues: e.eigenvalues.as_dvec3(),
256 eigenvectors: e.eigenvectors.as_dmat3(),
257 }
258 }
259}
260
261#[cfg(feature = "f64")]
262impl From<DSymmetricEigen3> for SymmetricEigen3 {
263 #[inline]
264 fn from(e: DSymmetricEigen3) -> Self {
265 Self {
266 eigenvalues: e.eigenvalues.as_vec3(),
267 eigenvectors: e.eigenvectors.as_mat3(),
268 }
269 }
270}
271
272#[cfg(feature = "f64")]
273impl From<SymmetricEigen3A> for DSymmetricEigen3 {
274 #[inline]
275 fn from(e: SymmetricEigen3A) -> Self {
276 Self {
277 eigenvalues: e.eigenvalues.as_dvec3(),
278 eigenvectors: e.eigenvectors.as_dmat3(),
279 }
280 }
281}
282
283#[cfg(feature = "f64")]
284impl From<DSymmetricEigen3> for SymmetricEigen3A {
285 #[inline]
286 fn from(e: DSymmetricEigen3) -> Self {
287 Self {
288 eigenvalues: e.eigenvalues.as_vec3a(),
289 eigenvectors: e.eigenvectors.as_mat3().into(),
290 }
291 }
292}
293
294impl From<SymmetricEigen3> for SymmetricEigen3A {
296 #[inline]
297 fn from(e: SymmetricEigen3) -> Self {
298 Self {
299 eigenvalues: e.eigenvalues.into(),
300 eigenvectors: glam::Mat3A::from(e.eigenvectors),
301 }
302 }
303}
304
305impl From<SymmetricEigen3A> for SymmetricEigen3 {
306 #[inline]
307 fn from(e: SymmetricEigen3A) -> Self {
308 Self {
309 eigenvalues: e.eigenvalues.into(),
310 eigenvectors: glam::Mat3::from(e.eigenvectors),
311 }
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use approx::assert_relative_eq;
319
320 #[test]
321 fn eigen_3x3() {
322 let mat =
323 glam::Mat3::from_cols_array_2d(&[[2.0, 7.0, 8.0], [7.0, 6.0, 3.0], [8.0, 3.0, 0.0]]);
324 let eigen = SymmetricEigen3::new(mat);
325
326 assert_relative_eq!(
327 eigen.eigenvalues,
328 glam::Vec3::new(-7.6051016780, 0.5774961930, 15.0276054850),
329 epsilon = 1.0e-6
330 );
331 assert_relative_eq!(
332 glam::Mat3::from_cols(
333 eigen.eigenvectors.x_axis,
334 eigen.eigenvectors.y_axis,
335 eigen.eigenvectors.z_axis
336 ),
337 glam::Mat3::from_cols(
338 glam::Vec3::new(1.0754474819, -0.3328260589, -1.0).normalize(),
339 glam::Vec3::new(-0.5420669703, 1.2530131898, -1.0).normalize(),
340 glam::Vec3::new(1.3587443369, 1.3858835966, 1.0).normalize()
341 ),
342 epsilon = 1.0e-6
343 );
344 }
345
346 #[test]
347 fn eigen_3x3_diagonal() {
348 let mat =
349 glam::Mat3::from_cols_array_2d(&[[2.0, 0.0, 0.0], [0.0, 5.0, 0.0], [0.0, 0.0, 3.0]]);
350 let eigen = SymmetricEigen3::new(mat);
351
352 assert_eq!(eigen.eigenvalues, glam::Vec3::new(2.0, 3.0, 5.0));
353 assert_eq!(
354 glam::Mat3::from_cols(
355 eigen.eigenvectors.x_axis.normalize().abs(),
356 eigen.eigenvectors.y_axis.normalize().abs(),
357 eigen.eigenvectors.z_axis.normalize().abs()
358 ),
359 glam::Mat3::from_cols_array_2d(&[[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]])
360 );
361 }
362
363 #[cfg(feature = "f64")]
364 #[test]
365 fn eigen_3x3_f64() {
366 let mat =
367 glam::DMat3::from_cols_array_2d(&[[2.0, 7.0, 8.0], [7.0, 6.0, 3.0], [8.0, 3.0, 0.0]]);
368 let eigen = DSymmetricEigen3::new(mat);
369
370 assert_relative_eq!(
371 eigen.eigenvalues,
372 glam::DVec3::new(-7.6051016780, 0.5774961930, 15.0276054850),
373 epsilon = 1.0e-8
374 );
375 }
376
377 #[test]
378 fn eigen_3x3_identity() {
379 let mat = glam::Mat3::IDENTITY;
382 let eigen = SymmetricEigen3::new(mat);
383
384 assert_eq!(eigen.eigenvalues, glam::Vec3::ONE);
385
386 assert_relative_eq!(
388 eigen.eigenvectors * eigen.eigenvectors.transpose(),
389 glam::Mat3::IDENTITY,
390 epsilon = 1e-6
391 );
392 }
393
394 #[test]
395 fn eigen_3x3_uniform_scaling() {
396 let mat = glam::Mat3::from_diagonal(glam::Vec3::splat(5.0));
398 let eigen = SymmetricEigen3::new(mat);
399
400 assert_eq!(eigen.eigenvalues, glam::Vec3::splat(5.0));
401
402 assert_relative_eq!(
404 eigen.eigenvectors * eigen.eigenvectors.transpose(),
405 glam::Mat3::IDENTITY,
406 epsilon = 1e-6
407 );
408 }
409}