#[cfg(not(feature = "std"))]
use simba::scalar::ComplexField;
use crate::eigen3::{DSymmetricEigen3, SymmetricEigen3};
use crate::SymmetricEigen3A;
macro_rules! impl_svd3 {
($Svd3:ident, $Mat3:ty, $Vec3:ty, $SymmetricEigen3:ty, $Real:ty $(, #[$attr:meta])*) => {
#[doc = concat!("The SVD of a 3x3 matrix (", stringify!($Real), " precision).")]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
$(#[$attr])*
pub struct $Svd3 {
/// The left orthogonal matrix U.
pub u: $Mat3,
pub s: $Vec3,
pub vt: $Mat3,
}
impl $Svd3 {
#[inline]
pub fn new(u: $Mat3, s: $Vec3, vt: $Mat3) -> Self {
Self { u, s, vt }
}
#[inline]
pub fn from_matrix(a: $Mat3) -> Self {
let ata = a.transpose() * a;
let eigen = <$SymmetricEigen3>::new(ata).reverse();
let s = <$Vec3>::new(
eigen.eigenvalues.x.max(0.0).sqrt(),
eigen.eigenvalues.y.max(0.0).sqrt(),
eigen.eigenvalues.z.max(0.0).sqrt(),
);
let v = eigen.eigenvectors;
let epsilon: $Real = 1e-10;
let u_col0_raw = if s.x > epsilon {
(a * v.col(0)) / s.x
} else {
v.col(0)
};
let u_col0 = u_col0_raw.normalize();
let u_col1_raw = if s.y > epsilon {
(a * v.col(1)) / s.y
} else {
Self::any_orthogonal(u_col0)
};
let u_col1 = (u_col1_raw - u_col0 * u_col1_raw.dot(u_col0)).normalize();
let u_col2_raw = if s.z > epsilon {
(a * v.col(2)) / s.z
} else {
u_col0.cross(u_col1)
};
let u_col2_orth = u_col2_raw - u_col0 * u_col2_raw.dot(u_col0) - u_col1 * u_col2_raw.dot(u_col1);
let u_col2 = u_col2_orth.normalize();
let u = <$Mat3>::from_cols(u_col0, u_col1, u_col2);
Self {
u,
s,
vt: v.transpose(),
}
}
#[inline]
fn any_orthogonal(v: $Vec3) -> $Vec3 {
let abs_v = <$Vec3>::new(v.x.abs(), v.y.abs(), v.z.abs());
let other = if abs_v.x <= abs_v.y && abs_v.x <= abs_v.z {
<$Vec3>::X
} else if abs_v.y <= abs_v.z {
<$Vec3>::Y
} else {
<$Vec3>::Z
};
v.cross(other).normalize()
}
#[inline]
pub fn recompose(&self) -> $Mat3 {
let u_s = <$Mat3>::from_cols(
self.u.col(0) * self.s.x,
self.u.col(1) * self.s.y,
self.u.col(2) * self.s.z,
);
u_s * self.vt
}
}
};
}
impl_svd3!(Svd3, glam::Mat3, glam::Vec3, SymmetricEigen3, f32);
impl_svd3!(Svd3A, glam::Mat3A, glam::Vec3A, SymmetricEigen3A, f32);
impl_svd3!(DSvd3, glam::DMat3, glam::DVec3, DSymmetricEigen3, f64);
impl From<Svd3> for DSvd3 {
#[inline]
fn from(svd: Svd3) -> Self {
Self {
u: svd.u.as_dmat3(),
s: svd.s.as_dvec3(),
vt: svd.vt.as_dmat3(),
}
}
}
impl From<DSvd3> for Svd3 {
#[inline]
fn from(svd: DSvd3) -> Self {
Self {
u: svd.u.as_mat3(),
s: svd.s.as_vec3(),
vt: svd.vt.as_mat3(),
}
}
}
impl From<Svd3A> for DSvd3 {
#[inline]
fn from(svd: Svd3A) -> Self {
Self {
u: svd.u.as_dmat3(),
s: svd.s.as_dvec3(),
vt: svd.vt.as_dmat3(),
}
}
}
impl From<DSvd3> for Svd3A {
#[inline]
fn from(svd: DSvd3) -> Self {
Self {
u: svd.u.as_mat3().into(),
s: svd.s.as_vec3a(),
vt: svd.vt.as_mat3().into(),
}
}
}
impl From<Svd3> for Svd3A {
#[inline]
fn from(svd: Svd3) -> Self {
Self {
u: glam::Mat3A::from(svd.u),
s: svd.s.into(),
vt: glam::Mat3A::from(svd.vt),
}
}
}
impl From<Svd3A> for Svd3 {
#[inline]
fn from(svd: Svd3A) -> Self {
Self {
u: glam::Mat3::from(svd.u),
s: svd.s.into(),
vt: glam::Mat3::from(svd.vt),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn svd_3x3_identity() {
let mat = glam::Mat3::IDENTITY;
let svd = Svd3::from_matrix(mat);
assert_relative_eq!(svd.s, glam::Vec3::new(1.0, 1.0, 1.0), epsilon = 1e-5);
assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-5);
}
#[test]
fn svd_3x3_diagonal() {
let mat = glam::Mat3::from_diagonal(glam::Vec3::new(4.0, 3.0, 2.0));
let svd = Svd3::from_matrix(mat);
assert_relative_eq!(svd.s, glam::Vec3::new(4.0, 3.0, 2.0), epsilon = 1e-5);
assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-5);
}
#[test]
fn svd_3x3_general() {
let mat =
glam::Mat3::from_cols_array_2d(&[[1.0, 4.0, 7.0], [2.0, 5.0, 9.0], [3.0, 6.0, 10.0]]);
let svd = Svd3::from_matrix(mat);
assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-2);
assert!(svd.s.x >= svd.s.y);
assert!(svd.s.y >= svd.s.z);
assert_relative_eq!(
svd.u * svd.u.transpose(),
glam::Mat3::IDENTITY,
epsilon = 1e-5
);
assert_relative_eq!(
svd.vt * svd.vt.transpose(),
glam::Mat3::IDENTITY,
epsilon = 1e-5
);
}
#[test]
fn svd_3x3_rotation() {
let mat = glam::Mat3::from_rotation_x(0.5) * glam::Mat3::from_rotation_y(0.3);
let svd = Svd3::from_matrix(mat);
assert_relative_eq!(svd.s, glam::Vec3::ONE, epsilon = 1e-5);
assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-5);
}
#[test]
fn svd_3x3_f64() {
let mat =
glam::DMat3::from_cols_array_2d(&[[1.0, 4.0, 7.0], [2.0, 5.0, 9.0], [3.0, 6.0, 10.0]]);
let svd = DSvd3::from_matrix(mat);
assert_relative_eq!(svd.recompose(), mat, epsilon = 1e-8);
}
#[test]
fn svd_3x3_rank_deficient() {
let mat =
glam::Mat3::from_cols_array_2d(&[[1.0, 4.0, 7.0], [2.0, 5.0, 8.0], [3.0, 6.0, 9.0]]);
let svd = Svd3::from_matrix(mat);
assert_relative_eq!(svd.recompose(), mat, epsilon = 0.02);
assert!(svd.s.z < 0.1);
}
}