1pub use phys_geom::math::{vec3, Real};
16
17pub type Vec3 = na::Vector3<Real>;
18pub type UnitQuat = na::UnitQuaternion<Real>;
19pub type Quat = na::Quaternion<Real>;
20pub type Mat3 = na::Matrix3<Real>;
21pub type UnitVec3 = na::UnitVector3<Real>;
22pub type Isometry3 = na::Isometry3<Real>;
23
24#[cfg(feature = "f64")]
25pub const PI: Real = std::f64::consts::PI;
26#[cfg(not(feature = "f64"))]
27pub const PI: Real = std::f32::consts::PI;
28
29#[inline]
30pub fn unit_quat(x: Real, y: Real, z: Real, w: Real) -> UnitQuat {
31 UnitQuat::from_quaternion(Quat::new(w, x, y, z))
32}
33
34pub trait UnitQuatExt {
35 fn is_near_identity(&self) -> bool;
36}
37
38impl UnitQuatExt for UnitQuat {
39 #[inline]
40 fn is_near_identity(&self) -> bool {
41 let positive_w_angle = self.w.abs().acos() * 2.0;
42 positive_w_angle < 0.002_847_144_6
43 }
44}
45
46pub trait Mat3Ext {
47 fn diagonalize(self) -> (Vec3, UnitQuat);
49}
50
51impl Mat3Ext for Mat3 {
52 fn diagonalize(self) -> (Vec3, UnitQuat) {
54 fn indexed_rotation(axis: usize, s: Real, c: Real) -> Quat {
55 let mut v = [0.; 3];
56 v[axis] = s;
57 Quat::new(c, v[0], v[1], v[2])
58 }
59
60 #[inline]
61 fn get_next_index(i: usize) -> usize {
62 (i + 1 + (i >> 1)) & 3
63 }
64
65 const MAX_ITERS: u32 = 24;
68 let mut q = UnitQuat::identity();
69 let mut d = Mat3::zeros();
70 for _ in 0..MAX_ITERS {
71 let axes = Mat3::from(q);
72 d = axes.transpose() * self * axes;
73 let d0 = d[(2, 1)].abs();
74 let d1 = d[(2, 0)].abs();
75 let d2 = d[(1, 0)].abs();
76 let a: usize = if d0 > d1 && d0 > d2 {
77 0
78 } else if d1 > d2 {
79 1
80 } else {
81 2
82 }; let a1 = get_next_index(a);
85 let a2 = get_next_index(a1);
86 if d[(a2, a1)] == 0. || (d[(a1, a1)] - d[(a2, a2)]).abs() > 2e6 * 2. * d[(a2, a1)].abs()
87 {
88 break;
89 }
90
91 let w = (d[(a1, a1)] - d[(a2, a2)]) / (2. * d[(a2, a1)]); let absw = w.abs();
93
94 let r = if absw > 1000. {
95 indexed_rotation(a, 1. / (4. * w), 1.) } else {
98 let t = 1. / (absw + (w * w + 1.).sqrt()); let h = 1. / (t * t + 1.).sqrt(); assert!(h != 1.); indexed_rotation(
102 a,
103 ((1. - h) / 2.).sqrt() * w.signum(),
104 ((1. + h) / 2.).sqrt(),
105 )
106 };
107 q = UnitQuat::new_normalize(q.into_inner() * r);
108 }
109
110 (d.diagonal(), q)
111 }
112}
113
114#[inline]
115pub(crate) fn is_near_zero(v: Vec3, epsilon: Real) -> bool {
116 v.x.abs() < epsilon && v.y.abs() < epsilon && v.z.abs() < epsilon
117}
118
119#[cfg(test)]
120mod tests {
121 use approx::assert_relative_eq;
122 use phys_geom::math::vec3;
123
124 use super::*;
125
126 #[test]
127 #[allow(clippy::excessive_precision)]
128 fn test_diagonalize_input_is_diagonal() {
129 const EPS: f32 = 1e-5;
130 let m = Mat3::from_column_slice(&[
131 1706.66675,
132 0.000_000_00,
133 0.000_000_00,
134 0.000_000_00,
135 1706.66675,
136 0.000_000_00,
137 0.000_000_00,
138 0.000_000_00,
139 1706.66675,
140 ]);
141 let (diag, rot) = m.diagonalize();
142 let expected_diag = vec3(1706.66675, 1706.66675, 1706.66675);
143 let expected_rot = UnitQuat::identity();
144 assert_relative_eq!(diag, expected_diag, epsilon = EPS);
145 assert_relative_eq!(rot, expected_rot, epsilon = EPS);
146 }
147
148 #[test]
149 #[allow(clippy::excessive_precision)]
150 fn test_diagonalize_input_is_close_to_diagonal() {
151 const EPS: f32 = 1e-5;
152
153 let m = Mat3::from_column_slice(&[
154 70_689_968.0,
155 -0.282_659_560,
156 0.038_694_873_5,
157 -0.282_659_560,
158 70_690_080.0,
159 -0.024_899_311_4,
160 0.038_694_873_5,
161 -0.024_899_311_4,
162 47_165_804.0,
163 ]);
164
165 let (diag, rot) = m.diagonalize();
166 let expected_diag = vec3(70_689_976.0, 70_690_088.0, 47_165_804.0);
167 let expected_rot = unit_quat(0., 0., 0.001_244_877_81, 0.999_999_225);
168 assert_relative_eq!(diag, expected_diag, epsilon = EPS);
169 assert_relative_eq!(rot, expected_rot, epsilon = EPS);
170 }
171
172 #[test]
173 fn test_diagonalize_random() {
174 let m = Mat3::from_column_slice(&[1., 1., 3., 1., 5., 1., 3., 1., 1.]);
175 let (diag, rot) = m.diagonalize();
176 let restored_m =
177 Mat3::from(rot) * Mat3::from_diagonal(&diag) * Mat3::from(rot).try_inverse().unwrap();
178 assert_relative_eq!(m, restored_m, epsilon = 1e-5);
179 }
180}