#[macro_use]
mod support;
use glam_det::nums::{
f32x4,
num_traits::{Bool, Num, PartialOrdEx},
};
trait AngleDiff {
type Output;
fn angle_diff(self, other: Self) -> Self::Output;
fn diff_small(self, other: Self, eps: Self) -> bool;
}
#[macro_export]
macro_rules! impl_angle_diff {
($t:ty, $pi:expr) => {
impl AngleDiff for $t {
type Output = $t;
fn angle_diff(self, other: $t) -> $t {
const PI2: $t = $pi + $pi;
let s = self.rem_euclid(PI2);
let o = other.rem_euclid(PI2);
if s > o {
(s - o).min(PI2 + o - s)
} else {
(o - s).min(PI2 + s - o)
}
}
fn diff_small(self, other: $t, eps: $t) -> bool {
self.angle_diff(other) < eps
}
}
};
}
impl_angle_diff!(f32, core::f32::consts::PI);
impl_angle_diff!(f64, core::f64::consts::PI);
impl AngleDiff for f32x4 {
type Output = f32x4;
fn angle_diff(self, other: f32x4) -> f32x4 {
const PI2: f32 = core::f32::consts::PI + core::f32::consts::PI;
let s = self.map_lanes(|x| f32::rem_euclid(x, PI2));
let o = other.map_lanes(|x| f32::rem_euclid(x, PI2));
let res0 = (s - o).min(f32x4::const_splat(PI2) + o - s);
let res1 = (o - s).min(f32x4::const_splat(PI2) + s - o);
f32x4::select(res0, s.lt(o), res1)
}
fn diff_small(self, other: f32x4, eps: f32x4) -> bool {
self.angle_diff(other).lt(eps).all()
}
}
#[macro_export]
macro_rules! assert_approx_angle {
($a:expr, $b:expr, $eps:expr) => {{
let (a, b) = ($a, $b);
let eps = $eps;
let diff = a.angle_diff(b);
assert!(
a.diff_small(b, $eps),
"assertion failed: `(left !== right)` \
(left: `{:?}`, right: `{:?}`, expect diff: `{:?}`, real diff: `{:?}`)",
a,
b,
eps,
diff
);
}};
}
pub(crate) trait Splat<T> {
fn splat(x: T) -> Self;
}
impl Splat<i32> for f32 {
fn splat(x: i32) -> Self {
x as f32
}
}
impl Splat<i32> for f64 {
fn splat(x: i32) -> Self {
x as f64
}
}
impl Splat<i32> for f32x4 {
fn splat(x: i32) -> Self {
f32x4::const_splat(x as f32)
}
}
impl Splat<f32> for f32 {
fn splat(x: f32) -> Self {
x
}
}
impl Splat<f32> for f64 {
fn splat(x: f32) -> Self {
x as f64
}
}
impl Splat<f32> for f32x4 {
fn splat(x: f32) -> Self {
f32x4::const_splat(x)
}
}
#[macro_export]
macro_rules! impl_3axis_test {
($name:ident, $t:ty, $quat:ident, $unit_quat:ident, $euler:path, $U:path, $V:path, $W:path, $vec:ident, $as_general_fn:ident) => {
glam_test!($name, {
use $crate::Splat;
let euler = $euler;
assert!($U != $W); for u in (-360..=360).step_by(20) {
for v in (-90..=90).step_by(10) {
for w in (-360..=360).step_by(20) {
let u1 = <$t as Splat<i32>>::splat(u).to_radians();
let v1 = <$t as Splat<i32>>::splat(v).to_radians();
let w1 = <$t as Splat<i32>>::splat(w).to_radians();
let q1: $unit_quat = ($unit_quat::from_axis_angle($U, u1)
* $unit_quat::from_axis_angle($V, v1)
* $unit_quat::from_axis_angle($W, w1));
let q2: $unit_quat = $unit_quat::from_euler(euler, u1, v1, w1);
assert_approx_eq!(q1, q2, 1e-5);
let q2_prime: $quat = $quat::from_euler(euler, u1, v1, w1);
assert_approx_eq!(q1.x, q2_prime.x, 1e-5);
assert_approx_eq!(q1.y, q2_prime.y, 1e-5);
assert_approx_eq!(q1.z, q2_prime.z, 1e-5);
assert_approx_eq!(q1.w, q2_prime.w, 1e-5);
let (u2, v2, w2) = q2.to_euler(euler);
let q3 = $unit_quat::from_euler(euler, u2, v2, w2);
assert_quat_approx_eq!(q2, q3, 1e-3);
if v.abs() != 90 {
assert_approx_angle!(u1, u2, <$t as Splat<f32>>::splat(1e-5));
assert_approx_angle!(v1, v2, <$t as Splat<f32>>::splat(1e-5));
assert_approx_angle!(w1, w2, <$t as Splat<f32>>::splat(1e-5));
}
assert_approx_eq!(
q1 * $vec::X.$as_general_fn(),
q3 * $vec::X.$as_general_fn(),
1e-3
);
assert_approx_eq!(
q1 * $vec::Y.$as_general_fn(),
q3 * $vec::Y.$as_general_fn(),
1e-3
);
assert_approx_eq!(
q1 * $vec::Z.$as_general_fn(),
q3 * $vec::Z.$as_general_fn(),
1e-3
);
}
}
}
});
};
}
#[macro_export]
macro_rules! impl_2axis_test {
($name:ident, $t:ty, $quat:ident, $unit_quat:ident, $euler:path, $U:path, $V:path, $W:path, $vec:ident, $as_general_fn:ident) => {
glam_test!($name, {
use $crate::Splat;
#[allow(deprecated)]
let euler = $euler;
assert!($U == $W); for u in (-360..=360).step_by(20) {
for v in (0..=180).step_by(10) {
for w in (-360..=360).step_by(20) {
let u1 = <$t as Splat<i32>>::splat(u).to_radians();
let v1 = <$t as Splat<i32>>::splat(v).to_radians();
let w1 = <$t as Splat<i32>>::splat(w).to_radians();
let q1: $unit_quat = ($unit_quat::from_axis_angle($U, u1)
* $unit_quat::from_axis_angle($V, v1)
* $unit_quat::from_axis_angle($W, w1));
let q2: $unit_quat = $unit_quat::from_euler(euler, u1, v1, w1);
assert_approx_eq!(q1, q2, 1e-5);
let q2_prime: $quat = $quat::from_euler(euler, u1, v1, w1);
assert_approx_eq!(q1.x, q2_prime.x, 1e-5);
assert_approx_eq!(q1.y, q2_prime.y, 1e-5);
assert_approx_eq!(q1.z, q2_prime.z, 1e-5);
assert_approx_eq!(q1.w, q2_prime.w, 1e-5);
let (u2, v2, w2) = q2.to_euler(euler);
let q3 = $unit_quat::from_euler(euler, u2, v2, w2);
assert_quat_approx_eq!(q2, q3, 1e-3);
if v != 0 && v != 180 {
assert_approx_angle!(u1, u2, <$t as Splat<f32>>::splat(1e-5));
assert_approx_angle!(v1, v2, <$t as Splat<f32>>::splat(1e-5));
assert_approx_angle!(w1, w2, <$t as Splat<f32>>::splat(1e-5));
}
assert_approx_eq!(
q1 * $vec::X.$as_general_fn(),
q3 * $vec::X.$as_general_fn(),
1e-3
);
assert_approx_eq!(
q1 * $vec::Y.$as_general_fn(),
q3 * $vec::Y.$as_general_fn(),
1e-3
);
assert_approx_eq!(
q1 * $vec::Z.$as_general_fn(),
q3 * $vec::Z.$as_general_fn(),
1e-3
);
}
}
}
});
};
}
macro_rules! impl_all_quat_tests_three_axis {
($t:ty, $quat: ident, $unit_quat:ident, $v:ident, $as_general_fn:ident) => {
impl_3axis_test!(
test_euler_zyx,
$t,
$quat,
$unit_quat,
ER::ZYX,
$v::Z,
$v::Y,
$v::X,
$v,
$as_general_fn
);
impl_3axis_test!(
test_euler_zxy,
$t,
$quat,
$unit_quat,
ER::ZXY,
$v::Z,
$v::X,
$v::Y,
$v,
$as_general_fn
);
impl_3axis_test!(
test_euler_yxz,
$t,
$quat,
$unit_quat,
ER::YXZ,
$v::Y,
$v::X,
$v::Z,
$v,
$as_general_fn
);
impl_3axis_test!(
test_euler_yzx,
$t,
$quat,
$unit_quat,
ER::YZX,
$v::Y,
$v::Z,
$v::X,
$v,
$as_general_fn
);
impl_3axis_test!(
test_euler_xyz,
$t,
$quat,
$unit_quat,
ER::XYZ,
$v::X,
$v::Y,
$v::Z,
$v,
$as_general_fn
);
impl_3axis_test!(
test_euler_xzy,
$t,
$quat,
$unit_quat,
ER::XZY,
$v::X,
$v::Z,
$v::Y,
$v,
$as_general_fn
);
};
}
macro_rules! impl_all_quat_tests_two_axis {
($t:ty, $quat: ident, $unit_quat:ident, $v:ident, $as_general_fn:ident) => {
impl_2axis_test!(
test_euler_zyz,
$t,
$quat,
$unit_quat,
ER::ZYZ,
$v::Z,
$v::Y,
$v::Z,
$v,
$as_general_fn
);
impl_2axis_test!(
test_euler_zxz,
$t,
$quat,
$unit_quat,
ER::ZXZ,
$v::Z,
$v::X,
$v::Z,
$v,
$as_general_fn
);
impl_2axis_test!(
test_euler_yxy,
$t,
$quat,
$unit_quat,
ER::YXY,
$v::Y,
$v::X,
$v::Y,
$v,
$as_general_fn
);
impl_2axis_test!(
test_euler_yzy,
$t,
$quat,
$unit_quat,
ER::YZY,
$v::Y,
$v::Z,
$v::Y,
$v,
$as_general_fn
);
impl_2axis_test!(
test_euler_xyx,
$t,
$quat,
$unit_quat,
ER::XYX,
$v::X,
$v::Y,
$v::X,
$v,
$as_general_fn
);
impl_2axis_test!(
test_euler_xzx,
$t,
$quat,
$unit_quat,
ER::XZX,
$v::X,
$v::Z,
$v::X,
$v,
$as_general_fn
);
};
}
mod euler {
use super::AngleDiff;
use glam_det::nums::f32x4;
use glam_det::*;
type ER = EulerRot;
mod quat {
use super::*;
impl_all_quat_tests_three_axis!(f32, Quat, UnitQuat, UnitVec3, as_vec3);
impl_all_quat_tests_two_axis!(f32, Quat, UnitQuat, UnitVec3, as_vec3);
}
mod dquat {
use super::*;
impl_all_quat_tests_three_axis!(f64, DQuat, UnitDQuat, UnitDVec3, as_dvec3);
impl_all_quat_tests_two_axis!(f64, DQuat, UnitDQuat, UnitDVec3, as_dvec3);
}
mod quatx4 {
use super::*;
impl_all_quat_tests_three_axis!(f32x4, Quatx4, UnitQuatx4, UnitVec3x4, as_vec3x4);
impl_all_quat_tests_two_axis!(f32x4, Quatx4, UnitQuatx4, UnitVec3x4, as_vec3x4);
}
}