1 #[macro_use]
2 mod support;
3 
4 /// Helper to get the 'canonical' version of a `Quat`. We define the canonical of quat `q` as:
5 /// * `q`, if q.w > epsilon
6 /// * `-q`, if q.w < -epsilon
7 /// * `(0, 0, 0, 1)` otherwise
8 /// The rationale is that q and -q represent the same rotation, and any (_, _, _, 0) respresent no rotation at all.
9 trait CanonicalQuat: Copy {
canonical(self) -> Self10     fn canonical(self) -> Self;
11 }
12 
13 macro_rules! impl_canonical_quat {
14     ($t:ty) => {
15         impl CanonicalQuat for $t {
16             fn canonical(self) -> Self {
17                 match self {
18                     _ if self.w >= 1e-5 => self,
19                     _ if self.w <= -1e-5 => -self,
20                     _ => <$t>::from_xyzw(0.0, 0.0, 0.0, 1.0),
21                 }
22             }
23         }
24     };
25 }
26 
27 impl_canonical_quat!(glam::Quat);
28 impl_canonical_quat!(glam::DQuat);
29 
30 /// Helper to set some alternative epsilons based on the floating point type used
31 trait EulerEpsilon {
32     /// epsilon for comparing quaterions built from eulers and axis-angles
33     const Q_EPS: f32;
34 
35     /// epsilon for comparing quaternion round-tripped through eulers (quat -> euler -> quat)
36     const E_EPS: f32;
37 }
38 impl EulerEpsilon for f32 {
39     const Q_EPS: f32 = 1e-5;
40 
41     // The scalar-math and wasm paths seems to use a particularly bad implementation of the trig functions
42     #[cfg(any(feature = "scalar-math", target_arch = "wasm32"))]
43     const E_EPS: f32 = 2e-4;
44 
45     #[cfg(not(any(feature = "scalar-math", target_arch = "wasm32")))]
46     const E_EPS: f32 = 1e-5;
47 }
48 impl EulerEpsilon for f64 {
49     const Q_EPS: f32 = 1e-8;
50     const E_EPS: f32 = 1e-8;
51 }
52 
53 macro_rules! impl_3axis_test {
54     ($name:ident, $t:ty, $quat:ident, $euler:path, $U:path, $V:path, $W:path, $vec:ident) => {
55         glam_test!($name, {
56             let euler = $euler;
57             assert!($U != $W); // First and last axis must be different for three axis
58             for u in (-180..=180).step_by(15) {
59                 for v in (-180..=180).step_by(15) {
60                     for w in (-180..=180).step_by(15) {
61                         let u1 = (u as $t).to_radians();
62                         let v1 = (v as $t).to_radians();
63                         let w1 = (w as $t).to_radians();
64 
65                         let q1: $quat = ($quat::from_axis_angle($U, u1)
66                             * $quat::from_axis_angle($V, v1)
67                             * $quat::from_axis_angle($W, w1))
68                         .normalize();
69 
70                         // Test if the rotation is the expected
71                         let q2: $quat = $quat::from_euler(euler, u1, v1, w1).normalize();
72                         assert_approx_eq!(q1.canonical(), q2.canonical(), <$t>::Q_EPS);
73 
74                         // Test quat reconstruction from angles
75                         let (u2, v2, w2) = q2.to_euler(euler);
76                         let q3 = $quat::from_euler(euler, u2, v2, w2).normalize();
77                         assert_approx_eq!(
78                             q2.canonical(),
79                             q3.canonical(),
80                             <$t>::E_EPS,
81                             format!(
82                                 "angles {:?} -> {:?}",
83                                 (u, v, w),
84                                 (u2.to_degrees(), v2.to_degrees(), w2.to_degrees())
85                             )
86                         );
87                     }
88                 }
89             }
90         });
91     };
92 }
93 
94 macro_rules! impl_all_quat_tests_three_axis {
95     ($t:ty, $q:ident, $v:ident) => {
96         impl_3axis_test!(test_euler_zyx, $t, $q, ER::ZYX, $v::Z, $v::Y, $v::X, $v);
97         impl_3axis_test!(test_euler_zxy, $t, $q, ER::ZXY, $v::Z, $v::X, $v::Y, $v);
98         impl_3axis_test!(test_euler_yxz, $t, $q, ER::YXZ, $v::Y, $v::X, $v::Z, $v);
99         impl_3axis_test!(test_euler_yzx, $t, $q, ER::YZX, $v::Y, $v::Z, $v::X, $v);
100         impl_3axis_test!(test_euler_xyz, $t, $q, ER::XYZ, $v::X, $v::Y, $v::Z, $v);
101         impl_3axis_test!(test_euler_xzy, $t, $q, ER::XZY, $v::X, $v::Z, $v::Y, $v);
102     };
103 }
104 
105 mod euler {
106     use super::{CanonicalQuat, EulerEpsilon};
107     use glam::*;
108     type ER = EulerRot;
109 
110     mod quat {
111         use super::*;
112 
113         impl_all_quat_tests_three_axis!(f32, Quat, Vec3);
114     }
115 
116     mod dquat {
117         use super::*;
118 
119         impl_all_quat_tests_three_axis!(f64, DQuat, DVec3);
120     }
121 }
122