From 27a349d282bbca65151c3b9fd544f7869b2f0edc Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Mon, 21 Mar 2022 16:08:53 -0500 Subject: [PATCH 1/4] fix for `UnitComplex::slerp()` #1093 --- src/geometry/unit_complex.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 48405dd4..5d3e45b2 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -410,7 +410,8 @@ where #[inline] #[must_use] pub fn slerp(&self, other: &Self, t: T) -> Self { - Self::new(self.angle() * (T::one() - t.clone()) + other.angle() * t) + let delta = other / self; + self * Self::new(delta.angle()*t) } } From b02e4ec2a989ad2ddab9eff020e6bade8177c711 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Sat, 26 Mar 2022 17:32:12 -0500 Subject: [PATCH 2/4] fixed cargo fmt error --- src/geometry/unit_complex.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geometry/unit_complex.rs b/src/geometry/unit_complex.rs index 5d3e45b2..b9aeaad4 100755 --- a/src/geometry/unit_complex.rs +++ b/src/geometry/unit_complex.rs @@ -411,7 +411,7 @@ where #[must_use] pub fn slerp(&self, other: &Self, t: T) -> Self { let delta = other / self; - self * Self::new(delta.angle()*t) + self * Self::new(delta.angle() * t) } } From baa320d7f31e80324ad5e60b51bfb34ca93ae7f1 Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Tue, 29 Mar 2022 13:38:10 -0500 Subject: [PATCH 3/4] added tests for complex and quaternion slerp pathing --- tests/geometry/rotation.rs | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index 9a29772e..bb7aacb6 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -33,7 +33,9 @@ fn quaternion_euler_angles_issue_494() { #[cfg(feature = "proptest-support")] mod proptest_tests { use na::{self, Rotation2, Rotation3, Unit}; + use na::{UnitComplex, UnitQuaternion}; use simba::scalar::RealField; + use approx::AbsDiffEq; use std::f64; use crate::proptest::*; @@ -229,5 +231,74 @@ mod proptest_tests { prop_assert_eq!(r, Rotation3::identity()) } } + + // + //In general, `slerp(a,b,t)` should equal `(b/a)^t * a` even though in practice, + //we may not use that formula directly for complex numbers or quaternions + // + + #[test] + fn slerp_powf_agree_2(a in unit_complex(), b in unit_complex(), t in PROPTEST_F64) { + let z1 = a.slerp(&b, t); + let z2 = (b/a).powf(t) * a; + prop_assert!(relative_eq!(z1,z2,epsilon=1e-10)); + } + + #[test] + fn slerp_powf_agree_3(a in unit_quaternion(), b in unit_quaternion(), t in PROPTEST_F64) { + if let Some(z1) = a.try_slerp(&b, t, f64::default_epsilon()) { + let z2 = (b/a).powf(t) * a; + prop_assert!(relative_eq!(z1,z2,epsilon=1e-10)); + } + } + + // + //when not antipodal, slerp should always take the shortest path between two orientations + // + + #[test] + fn slerp_takes_shortest_path_2( + z in unit_complex(), dtheta in -f64::pi()..f64::pi(), t in 0.0..1.0f64 + ) { + + //ambiguous when at ends of angle range, so we don't really care here + if dtheta.abs() != f64::pi() { + + //make two complex numbers separated by an angle between -pi and pi + let (z1, z2) = (z, z * UnitComplex::new(dtheta)); + let z3 = z1.slerp(&z2, t); + + //since the angle is no larger than a half-turn, and t is between 0 and 1, + //the shortest path just corresponds to adding the scaled angle + let a1 = z3.angle(); + let a2 = na::wrap(z1.angle() + dtheta*t, -f64::pi(), f64::pi()); + + prop_assert!(relative_eq!(a1, a2, epsilon=1e-10)); + } + + } + + #[test] + fn slerp_takes_shortest_path_3( + q in unit_quaternion(), dtheta in -f64::pi()..f64::pi(), t in 0.0..1.0f64 + ) { + + //ambiguous when at ends of angle range, so we don't really care here + if let Some(axis) = q.axis() { + + //make two quaternions separated by an angle between -pi and pi + let (q1, q2) = (q, q * UnitQuaternion::from_axis_angle(&axis, dtheta)); + let q3 = q1.slerp(&q2, t); + + //since the angle is no larger than a half-turn, and t is between 0 and 1, + //the shortest path just corresponds to adding the scaled angle + let q4 = q1 * UnitQuaternion::from_axis_angle(&axis, dtheta*t); + prop_assert!(relative_eq!(q3, q4, epsilon=1e-10)); + + } + + } + + } } From 96e2d1f69e390cd65928232d1222032e2e149f4b Mon Sep 17 00:00:00 2001 From: Joshua Smith Date: Tue, 29 Mar 2022 13:42:38 -0500 Subject: [PATCH 4/4] fixed cargo fmt --- tests/geometry/rotation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/geometry/rotation.rs b/tests/geometry/rotation.rs index bb7aacb6..84bba676 100644 --- a/tests/geometry/rotation.rs +++ b/tests/geometry/rotation.rs @@ -32,10 +32,10 @@ fn quaternion_euler_angles_issue_494() { #[cfg(feature = "proptest-support")] mod proptest_tests { + use approx::AbsDiffEq; use na::{self, Rotation2, Rotation3, Unit}; use na::{UnitComplex, UnitQuaternion}; use simba::scalar::RealField; - use approx::AbsDiffEq; use std::f64; use crate::proptest::*;