1#[cfg(not(feature = "std"))]
7use simba::scalar::ComplexField;
8
9use crate::eigen3::{DSymmetricEigen3, SymmetricEigen3};
10use crate::SymmetricEigen3A;
11
12macro_rules! impl_svd3 {
14 ($Svd3:ident, $Mat3:ty, $Vec3:ty, $SymmetricEigen3:ty, $Real:ty $(, #[$attr:meta])*) => {
15 #[doc = concat!("The SVD of a 3x3 matrix (", stringify!($Real), " precision).")]
16 #[derive(Clone, Copy, Debug, PartialEq)]
17 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18 $(#[$attr])*
19 pub struct $Svd3 {
20 pub u: $Mat3,
22 pub s: $Vec3,
24 pub vt: $Mat3,
26 }
27
28 impl $Svd3 {
29 #[inline]
31 pub fn new(u: $Mat3, s: $Vec3, vt: $Mat3) -> Self {
32 Self { u, s, vt }
33 }
34
35 #[inline]
44 pub fn from_matrix(a: $Mat3) -> Self {
45 let ata = a.transpose() * a;
47
48 let eigen = <$SymmetricEigen3>::new(ata).reverse(); let s = <$Vec3>::new(
55 eigen.eigenvalues.x.max(0.0).sqrt(),
56 eigen.eigenvalues.y.max(0.0).sqrt(),
57 eigen.eigenvalues.z.max(0.0).sqrt(),
58 );
59
60 let v = eigen.eigenvectors;
62
63 let epsilon: $Real = 1e-10;
66
67 let u_col0_raw = if s.x > epsilon {
69 (a * v.col(0)) / s.x
70 } else {
71 v.col(0)
72 };
73 let u_col0 = u_col0_raw.normalize();
74
75 let u_col1_raw = if s.y > epsilon {
76 (a * v.col(1)) / s.y
77 } else {
78 Self::any_orthogonal(u_col0)
79 };
80 let u_col1 = (u_col1_raw - u_col0 * u_col1_raw.dot(u_col0)).normalize();
82
83 let u_col2_raw = if s.z > epsilon {
84 (a * v.col(2)) / s.z
85 } else {
86 u_col0.cross(u_col1)
87 };
88 let u_col2_orth = u_col2_raw - u_col0 * u_col2_raw.dot(u_col0) - u_col1 * u_col2_raw.dot(u_col1);
90 let u_col2 = u_col2_orth.normalize();
91
92 let u = <$Mat3>::from_cols(u_col0, u_col1, u_col2);
93
94 Self {
95 u,
96 s,
97 vt: v.transpose(),
98 }
99 }
100
101 #[inline]
103 fn any_orthogonal(v: $Vec3) -> $Vec3 {
104 let abs_v = <$Vec3>::new(v.x.abs(), v.y.abs(), v.z.abs());
105 let other = if abs_v.x <= abs_v.y && abs_v.x <= abs_v.z {
106 <$Vec3>::X
107 } else if abs_v.y <= abs_v.z {
108 <$Vec3>::Y
109 } else {
110 <$Vec3>::Z
111 };
112 v.cross(other).normalize()
113 }
114
115 #[inline]
119 pub fn recompose(&self) -> $Mat3 {
120 let u_s = <$Mat3>::from_cols(
121 self.u.col(0) * self.s.x,
122 self.u.col(1) * self.s.y,
123 self.u.col(2) * self.s.z,
124 );
125 u_s * self.vt
126 }
127 }
128 };
129}
130
131impl_svd3!(Svd3, glam::Mat3, glam::Vec3, SymmetricEigen3, f32);
132impl_svd3!(Svd3A, glam::Mat3A, glam::Vec3A, SymmetricEigen3A, f32);
133impl_svd3!(DSvd3, glam::DMat3, glam::DVec3, DSymmetricEigen3, f64);
134
135impl From<Svd3> for DSvd3 {
137 #[inline]
138 fn from(svd: Svd3) -> Self {
139 Self {
140 u: svd.u.as_dmat3(),
141 s: svd.s.as_dvec3(),
142 vt: svd.vt.as_dmat3(),
143 }
144 }
145}
146
147impl From<DSvd3> for Svd3 {
148 #[inline]
149 fn from(svd: DSvd3) -> Self {
150 Self {
151 u: svd.u.as_mat3(),
152 s: svd.s.as_vec3(),
153 vt: svd.vt.as_mat3(),
154 }
155 }
156}
157
158impl From<Svd3A> for DSvd3 {
159 #[inline]
160 fn from(svd: Svd3A) -> Self {
161 Self {
162 u: svd.u.as_dmat3(),
163 s: svd.s.as_dvec3(),
164 vt: svd.vt.as_dmat3(),
165 }
166 }
167}
168
169impl From<DSvd3> for Svd3A {
170 #[inline]
171 fn from(svd: DSvd3) -> Self {
172 Self {
173 u: svd.u.as_mat3().into(),
174 s: svd.s.as_vec3a(),
175 vt: svd.vt.as_mat3().into(),
176 }
177 }
178}
179
180impl From<Svd3> for Svd3A {
182 #[inline]
183 fn from(svd: Svd3) -> Self {
184 Self {
185 u: glam::Mat3A::from(svd.u),
186 s: svd.s.into(),
187 vt: glam::Mat3A::from(svd.vt),
188 }
189 }
190}
191
192impl From<Svd3A> for Svd3 {
193 #[inline]
194 fn from(svd: Svd3A) -> Self {
195 Self {
196 u: glam::Mat3::from(svd.u),
197 s: svd.s.into(),
198 vt: glam::Mat3::from(svd.vt),
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use approx::assert_relative_eq;
207
208 #[test]
209 fn svd_3x3_identity() {
210 let mat = glam::Mat3::IDENTITY;
211 let svd = Svd3::from_matrix(mat);
212
213 assert_relative_eq!(svd.s, glam::Vec3::new(1.0, 1.0, 1.0), epsilon = 1e-5);
214 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-5);
215 }
216
217 #[test]
218 fn svd_3x3_diagonal() {
219 let mat = glam::Mat3::from_diagonal(glam::Vec3::new(4.0, 3.0, 2.0));
220 let svd = Svd3::from_matrix(mat);
221
222 assert_relative_eq!(svd.s, glam::Vec3::new(4.0, 3.0, 2.0), epsilon = 1e-5);
224 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-5);
225 }
226
227 #[test]
228 fn svd_3x3_general() {
229 let mat =
231 glam::Mat3::from_cols_array_2d(&[[1.0, 4.0, 7.0], [2.0, 5.0, 9.0], [3.0, 6.0, 10.0]]);
232 let svd = Svd3::from_matrix(mat);
233
234 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-2);
236
237 assert!(svd.s.x >= svd.s.y);
239 assert!(svd.s.y >= svd.s.z);
240
241 assert_relative_eq!(
243 svd.u * svd.u.transpose(),
244 glam::Mat3::IDENTITY,
245 epsilon = 1e-5
246 );
247
248 assert_relative_eq!(
250 svd.vt * svd.vt.transpose(),
251 glam::Mat3::IDENTITY,
252 epsilon = 1e-5
253 );
254 }
255
256 #[test]
257 fn svd_3x3_rotation() {
258 let mat = glam::Mat3::from_rotation_x(0.5) * glam::Mat3::from_rotation_y(0.3);
259 let svd = Svd3::from_matrix(mat);
260
261 assert_relative_eq!(svd.s, glam::Vec3::ONE, epsilon = 1e-5);
263 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-5);
264 }
265
266 #[test]
267 fn svd_3x3_f64() {
268 let mat =
269 glam::DMat3::from_cols_array_2d(&[[1.0, 4.0, 7.0], [2.0, 5.0, 9.0], [3.0, 6.0, 10.0]]);
270 let svd = DSvd3::from_matrix(mat);
271
272 assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-8);
273 }
274
275 #[test]
276 fn svd_3x3_rank_deficient() {
277 let mat =
279 glam::Mat3::from_cols_array_2d(&[[1.0, 4.0, 7.0], [2.0, 5.0, 8.0], [3.0, 6.0, 9.0]]);
280 let svd = Svd3::from_matrix(mat);
281
282 assert_relative_eq!(svd.recompose(), mat, epsilon = 0.02);
284
285 assert!(svd.s.z < 0.1);
287 }
288}