Repository: bitshifter/glam-rs
Pull request #130 (2021-02-12 12:51:15)

Add Quat::angle_between
This is very similar to the existing `Vec3::angle_between`

emilk: I'll cool down with the unsolicited PR:s after this one, I promise!
bitshifter: Are these all things that you are using at Embark, or from other projects?
bitshifter: Would be good to add to `CHANGELOG.md` as well
emilk: These are things we're using at Embark.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14e2930..4547c35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog], and this project adheres to
 * Added `try_normalize()` and `normalize_or_zero()` for all real vector types.
 * Added `from_diagonal` methods to all matrix types for creating diagonal
   matrices.
+* Added `Quat::angle_between`, `Quat::from_rotation_arc` and `Quat::from_rotation_arc_colinear`.
 
 ### Changed
 
diff --git a/src/core/traits/scalar.rs b/src/core/traits/scalar.rs
index 5dc3c8d..5c49d80 100644
--- a/src/core/traits/scalar.rs
+++ b/src/core/traits/scalar.rs
@@ -164,6 +164,7 @@ pub trait NumEx:
 pub trait SignedEx: Signed + NumEx {}
 
 pub trait FloatEx: Float + FloatConstEx + SignedEx {
+    /// Returns a very close approximation of `self.clamp(-1.0, 1.0).acos()`.
     fn acos_approx(self) -> Self;
     fn from_f32(f: f32) -> Self;
     fn from_f64(f: f64) -> Self;
diff --git a/src/quat.rs b/src/quat.rs
index 77667fe..9e8003f 100644
--- a/src/quat.rs
+++ b/src/quat.rs
@@ -252,6 +252,16 @@ macro_rules! impl_quat_methods {
             self.0.is_near_identity()
         }
 
+        /// Returns the angle (in radians) for the minimal rotation
+        /// for transforming this quaternion into another.
+        ///
+        /// Both quaternions must be normalized.
+        pub fn angle_between(self, other: Self) -> $t {
+            glam_assert!(self.is_normalized() && other.is_normalized());
+            use crate::core::traits::scalar::FloatEx;
+            self.dot(other).abs().acos_approx() * 2.0
+        }
+
         /// Returns true if the absolute difference of all elements between `self` and `other`
         /// is less than or equal to `max_abs_diff`.
         ///


Test patch:
diff --git a/tests/quat.rs b/tests/quat.rs
index 7b9433a..4427d69 100644
--- a/tests/quat.rs
+++ b/tests/quat.rs
@@ -182,6 +182,36 @@ macro_rules! impl_quat_tests {
             assert_approx_eq!(-$vec3::X, mrzx * $vec3::Y);
             assert_approx_eq!(-$vec3::X, mrzx.mul_vec3($vec3::Y));
         }
+
+        #[test]
+        fn test_angle_between() {
+            const TAU: $t = 2.0 * core::$t::consts::PI;
+            let eps = 10.0 * core::$t::EPSILON as f32;
+            let q1 = $quat::from_rotation_ypr(0.0, 0.0, 0.0);
+            let q2 = $quat::from_rotation_ypr(TAU * 0.25, 0.0, 0.0);
+            let q3 = $quat::from_rotation_ypr(TAU * 0.5, 0.0, 0.0);
+            let q4 = $quat::from_rotation_ypr(0.0, 0.0, TAU * 0.25);
+            let q5 = $quat::from_axis_angle($vec3::new(1.0, 2.0, 3.0).normalize(), TAU * 0.3718);
+            let q6 = $quat::from_axis_angle($vec3::new(-1.0, 5.0, 3.0).normalize(), TAU * 0.94);
+            assert_approx_eq!(q1.angle_between(q2), TAU * 0.25, eps);
+            assert_approx_eq!(q1.angle_between(q3), TAU * 0.5, eps);
+            assert_approx_eq!(q3.angle_between(q3), 0.0, eps);
+            assert_approx_eq!(q3.angle_between(-q3), 0.0, eps);
+            assert_approx_eq!((q4 * q2 * q2).angle_between(q4 * q2), TAU * 0.25, eps);
+            assert_approx_eq!(q1.angle_between(q5), TAU * 0.3718, eps);
+            assert_approx_eq!(
+                (q5 * q2 * q1).angle_between(q5 * q2 * q5),
+                TAU * 0.3718,
+                eps
+            );
+            assert_approx_eq!((q3 * q3).angle_between(q1), 0.0, eps);
+            assert_approx_eq!((q3 * q3 * q3).angle_between(q3), 0.0, eps);
+            assert_approx_eq!((q3 * q3 * q3 * q3).angle_between(q1), 0.0, eps);
+            assert_approx_eq!(q1.angle_between(q6), TAU - TAU * 0.94, eps);
+            assert_approx_eq!((q5 * q1).angle_between(q5 * q6), TAU - TAU * 0.94, eps);
+            assert_approx_eq!((q1 * q5).angle_between(q6 * q5), TAU - TAU * 0.94, eps);
+        }
+
         #[test]
         fn test_lerp() {
             let q0 = $quat::from_rotation_y(deg(0.0));