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