1use glam::Vec2Swizzles;
6
7#[cfg(not(feature = "std"))]
8use simba::scalar::ComplexField;
9
10macro_rules! impl_symmetric_eigen2 {
12 ($SymmetricEigen2:ident, $Mat2:ty, $Vec2:ty, $Real:ty $(, #[$attr:meta])*) => {
13 #[doc = concat!("The eigen decomposition of a symmetric 2x2 matrix (", stringify!($Real), " precision).")]
14 #[derive(Clone, Copy, Debug, PartialEq)]
15 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16 $(#[$attr])*
17 pub struct $SymmetricEigen2 {
18 pub eigenvalues: $Vec2,
20 pub eigenvectors: $Mat2,
22 }
23
24 impl $SymmetricEigen2 {
25 pub fn new(mat: $Mat2) -> Self {
30 let eigenvalues = Self::eigenvalues(mat);
31 let eigenvector1 = Self::eigenvector(mat, eigenvalues.x);
32 let eigenvector2 = Self::eigenvector(mat, eigenvalues.y);
33
34 Self {
35 eigenvalues,
36 eigenvectors: <$Mat2>::from_cols(eigenvector1, eigenvector2),
37 }
38 }
39
40 pub fn reverse(&self) -> Self {
42 Self {
43 eigenvalues: self.eigenvalues.yx(),
44 eigenvectors: <$Mat2>::from_cols(self.eigenvectors.y_axis, self.eigenvectors.x_axis),
45 }
46 }
47
48 pub fn eigenvalues(mat: $Mat2) -> $Vec2 {
52 let [a, b, c] = [
53 1.0,
54 -(mat.x_axis.x + mat.y_axis.y),
55 mat.x_axis.x * mat.y_axis.y - mat.x_axis.y * mat.y_axis.x,
56 ];
57 let sqrt_part = (b.powi(2) - 4.0 * a * c).sqrt();
61 let eigen1 = (-b + sqrt_part) / (2.0 * a);
62 let eigen2 = (-b - sqrt_part) / (2.0 * a);
63 <$Vec2>::new(eigen1, eigen2)
64 }
65
66 pub fn eigenvector(mat: $Mat2, eigenvalue: $Real) -> $Vec2 {
71 <$Vec2>::new(1.0, (eigenvalue - mat.x_axis.x) / mat.y_axis.x).normalize()
72 }
73 }
74 };
75}
76
77impl_symmetric_eigen2!(SymmetricEigen2, glam::Mat2, glam::Vec2, f32);
78impl_symmetric_eigen2!(DSymmetricEigen2, glam::DMat2, glam::DVec2, f64);
79
80impl From<SymmetricEigen2> for DSymmetricEigen2 {
82 #[inline]
83 fn from(e: SymmetricEigen2) -> Self {
84 Self {
85 eigenvalues: e.eigenvalues.as_dvec2(),
86 eigenvectors: e.eigenvectors.as_dmat2(),
87 }
88 }
89}
90
91impl From<DSymmetricEigen2> for SymmetricEigen2 {
92 #[inline]
93 fn from(e: DSymmetricEigen2) -> Self {
94 Self {
95 eigenvalues: e.eigenvalues.as_vec2(),
96 eigenvectors: e.eigenvectors.as_mat2(),
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use approx::assert_relative_eq;
105
106 #[test]
107 fn eigen_2x2() {
108 let mat = glam::Mat2::from_cols_array_2d(&[[6.0, 3.0], [3.0, 4.0]]);
109 let eigen = SymmetricEigen2::new(mat);
110
111 assert_relative_eq!(
112 eigen.eigenvalues,
113 glam::Vec2::new(8.16228, 1.83772),
114 epsilon = 0.001
115 );
116 assert_relative_eq!(
117 glam::Mat2::from_cols(eigen.eigenvectors.x_axis, eigen.eigenvectors.y_axis,),
118 glam::Mat2::from_cols(
119 glam::Vec2::new(0.811242, 0.58471),
120 glam::Vec2::new(0.58471, -0.811242),
121 ),
122 epsilon = 0.001
123 );
124 }
125
126 #[test]
127 fn eigen_2x2_f64() {
128 let mat = glam::DMat2::from_cols_array_2d(&[[6.0, 3.0], [3.0, 4.0]]);
129 let eigen = DSymmetricEigen2::new(mat);
130
131 assert_relative_eq!(
132 eigen.eigenvalues,
133 glam::DVec2::new(8.16227766016838, 1.8377223398316202),
134 epsilon = 1e-10
135 );
136 }
137}