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