#[macro_use] mod support; /// Helper to get the 'canonical' version of a `Quat`. We define the canonical of quat `q` as: /// * `q`, if q.w > epsilon /// * `-q`, if q.w < -epsilon /// * `(0, 0, 0, 1)` otherwise /// The rationale is that q and -q represent the same rotation, and any (_, _, _, 0) respresent no rotation at all. trait CanonicalQuat: Copy { fn canonical(self) -> Self; } macro_rules! impl_canonical_quat { ($t:ty) => { impl CanonicalQuat for $t { fn canonical(self) -> Self { match self { _ if self.w >= 1e-5 => self, _ if self.w <= -1e-5 => -self, _ => <$t>::from_xyzw(0.0, 0.0, 0.0, 1.0), } } } }; } impl_canonical_quat!(glam::Quat); impl_canonical_quat!(glam::DQuat); /// Helper to set some alternative epsilons based on the floating point type used trait EulerEpsilon { /// epsilon for comparing quaterions built from eulers and axis-angles const Q_EPS: f32; /// epsilon for comparing quaternion round-tripped through eulers (quat -> euler -> quat) const E_EPS: f32; } impl EulerEpsilon for f32 { const Q_EPS: f32 = 1e-5; // The scalar-math and wasm paths seems to use a particularly bad implementation of the trig functions #[cfg(any(feature = "scalar-math", target_arch = "wasm32"))] const E_EPS: f32 = 2e-4; #[cfg(not(any(feature = "scalar-math", target_arch = "wasm32")))] const E_EPS: f32 = 1e-5; } impl EulerEpsilon for f64 { const Q_EPS: f32 = 1e-8; const E_EPS: f32 = 1e-8; } macro_rules! impl_3axis_test { ($name:ident, $t:ty, $quat:ident, $euler:path, $U:path, $V:path, $W:path, $vec:ident) => { glam_test!($name, { let euler = $euler; assert!($U != $W); // First and last axis must be different for three axis for u in (-180..=180).step_by(15) { for v in (-180..=180).step_by(15) { for w in (-180..=180).step_by(15) { let u1 = (u as $t).to_radians(); let v1 = (v as $t).to_radians(); let w1 = (w as $t).to_radians(); let q1: $quat = ($quat::from_axis_angle($U, u1) * $quat::from_axis_angle($V, v1) * $quat::from_axis_angle($W, w1)) .normalize(); // Test if the rotation is the expected let q2: $quat = $quat::from_euler(euler, u1, v1, w1).normalize(); assert_approx_eq!(q1.canonical(), q2.canonical(), <$t>::Q_EPS); // Test quat reconstruction from angles let (u2, v2, w2) = q2.to_euler(euler); let q3 = $quat::from_euler(euler, u2, v2, w2).normalize(); assert_approx_eq!( q2.canonical(), q3.canonical(), <$t>::E_EPS, format!( "angles {:?} -> {:?}", (u, v, w), (u2.to_degrees(), v2.to_degrees(), w2.to_degrees()) ) ); } } } }); }; } macro_rules! impl_all_quat_tests_three_axis { ($t:ty, $q:ident, $v:ident) => { impl_3axis_test!(test_euler_zyx, $t, $q, ER::ZYX, $v::Z, $v::Y, $v::X, $v); impl_3axis_test!(test_euler_zxy, $t, $q, ER::ZXY, $v::Z, $v::X, $v::Y, $v); impl_3axis_test!(test_euler_yxz, $t, $q, ER::YXZ, $v::Y, $v::X, $v::Z, $v); impl_3axis_test!(test_euler_yzx, $t, $q, ER::YZX, $v::Y, $v::Z, $v::X, $v); impl_3axis_test!(test_euler_xyz, $t, $q, ER::XYZ, $v::X, $v::Y, $v::Z, $v); impl_3axis_test!(test_euler_xzy, $t, $q, ER::XZY, $v::X, $v::Z, $v::Y, $v); }; } mod euler { use super::{CanonicalQuat, EulerEpsilon}; use glam::*; type ER = EulerRot; mod quat { use super::*; impl_all_quat_tests_three_axis!(f32, Quat, Vec3); } mod dquat { use super::*; impl_all_quat_tests_three_axis!(f64, DQuat, DVec3); } }