1#[cfg(not(feature = "std"))]
4use simba::scalar::{ComplexField, RealField};
5
6macro_rules! impl_svd2 {
8 ($Svd2:ident, $Mat2:ty, $Vec2:ty, $Real:ty $(, #[$attr:meta])*) => {
9 #[doc = concat!("The SVD of a 2x2 matrix (", stringify!($Real), " precision).")]
10 #[derive(Clone, Copy, Debug, PartialEq)]
11 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12 $(#[$attr])*
13 pub struct $Svd2 {
14 pub u: $Mat2,
16 pub s: $Vec2,
18 pub vt: $Mat2,
20 }
21
22 impl $Svd2 {
23 #[inline]
25 pub fn new(u: $Mat2, s: $Vec2, vt: $Mat2) -> Self {
26 Self { u, s, vt }
27 }
28
29 #[inline]
38 pub fn from_matrix(m: $Mat2) -> Self {
39 let e = (m.col(0).x + m.col(1).y) * 0.5;
40 let f = (m.col(0).x - m.col(1).y) * 0.5;
41 let g = (m.col(0).y + m.col(1).x) * 0.5;
42 let h = (m.col(0).y - m.col(1).x) * 0.5;
43 let q = (e * e + h * h).sqrt();
44 let r = (f * f + g * g).sqrt();
45
46 let sx = q + r;
49 let sy = q - r;
50 let sy_sign: $Real = if sy < 0.0 { -1.0 } else { 1.0 };
51 let singular_values = <$Vec2>::new(sx, sy * sy_sign);
52
53 let a2 = h.atan2(e);
54 let a1 = if r < 1e-10 { a2 } else { g.atan2(f) };
57 let theta = (a2 - a1) * 0.5;
58 let phi = (a2 + a1) * 0.5;
59 let st = theta.sin();
60 let ct = theta.cos();
61 let sp = phi.sin();
62 let cp = phi.cos();
63
64 let u = <$Mat2>::from_cols(<$Vec2>::new(cp, sp), <$Vec2>::new(-sp, cp));
65 let vt = <$Mat2>::from_cols(
66 <$Vec2>::new(ct, st * sy_sign),
67 <$Vec2>::new(-st, ct * sy_sign),
68 );
69
70 Self {
71 u,
72 s: singular_values,
73 vt,
74 }
75 }
76
77 #[inline]
81 pub fn recompose(&self) -> $Mat2 {
82 let u_s = <$Mat2>::from_cols(self.u.col(0) * self.s.x, self.u.col(1) * self.s.y);
83 u_s * self.vt.transpose()
84 }
85
86 }
87 };
88}
89
90impl_svd2!(Svd2, glam::Mat2, glam::Vec2, f32);
91#[cfg(feature = "f64")]
92impl_svd2!(DSvd2, glam::DMat2, glam::DVec2, f64);
93
94#[cfg(feature = "f64")]
96impl From<Svd2> for DSvd2 {
97 #[inline]
98 fn from(svd: Svd2) -> Self {
99 Self {
100 u: svd.u.as_dmat2(),
101 s: svd.s.as_dvec2(),
102 vt: svd.vt.as_dmat2(),
103 }
104 }
105}
106
107#[cfg(feature = "f64")]
108impl From<DSvd2> for Svd2 {
109 #[inline]
110 fn from(svd: DSvd2) -> Self {
111 Self {
112 u: svd.u.as_mat2(),
113 s: svd.s.as_vec2(),
114 vt: svd.vt.as_mat2(),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use approx::assert_relative_eq;
123
124 #[test]
125 fn svd_2x2_identity() {
126 let mat = glam::Mat2::IDENTITY;
127 let svd = Svd2::from_matrix(mat);
128
129 assert_relative_eq!(svd.s, glam::Vec2::new(1.0, 1.0), epsilon = 1e-6);
130 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-6);
131 }
132
133 #[test]
134 fn svd_2x2_diagonal() {
135 let mat = glam::Mat2::from_diagonal(glam::Vec2::new(3.0, 2.0));
136 let svd = Svd2::from_matrix(mat);
137
138 assert_relative_eq!(svd.s, glam::Vec2::new(3.0, 2.0), epsilon = 1e-6);
139 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-6);
140 }
141
142 #[test]
143 fn svd_2x2_general() {
144 let mat = glam::Mat2::from_cols_array_2d(&[[4.0, 3.0], [2.0, 1.0]]);
145 let svd = Svd2::from_matrix(mat);
146
147 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-5);
149
150 assert!(svd.s.x >= svd.s.y);
152 assert!(svd.s.y >= 0.0);
153
154 assert_relative_eq!(
156 svd.u * svd.u.transpose(),
157 glam::Mat2::IDENTITY,
158 epsilon = 1e-6
159 );
160 assert_relative_eq!(
161 svd.vt * svd.vt.transpose(),
162 glam::Mat2::IDENTITY,
163 epsilon = 1e-6
164 );
165 }
166
167 #[test]
168 fn svd_2x2_rotation() {
169 let angle = 0.5_f32;
170 let mat = glam::Mat2::from_angle(angle);
171 let svd = Svd2::from_matrix(mat);
172
173 assert_relative_eq!(svd.s, glam::Vec2::new(1.0, 1.0), epsilon = 1e-6);
175 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-6);
176 }
177
178 #[cfg(feature = "f64")]
179 #[test]
180 fn svd_2x2_f64() {
181 let mat = glam::DMat2::from_cols_array_2d(&[[4.0, 3.0], [2.0, 1.0]]);
182 let svd = DSvd2::from_matrix(mat);
183
184 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-10);
185 }
186}