Repository: bitshifter/glam-rs
Pull request #591 (2024-12-16 21:35:27)

Return the target Quat when the angle is small
Returning source `Quat` is misleading as it skips the rotation completely.

See #590 

bitshifter: Thanks, I was wondering how this issue manifests? You expect to rotate to a target but it never quite reaches it under this angle?

Ignore the cargo-deny and clippy failures, they look unrelated to your change.
t-yrka: > Thanks, I was wondering how this issue manifests? You expect to rotate to a target but it never quite reaches it under this minimum angle?

I've encountered this issue while trying to rotate a slowly moving object toward a point that is quite far away. I was drawing the object's center point projection with a given rotation and observed that it was jittering and sometimes off (from the target) by quite a significant margin.

Based on the data I've extracted at some point it can be observed/reproduced with the following code:
```
use glam::{Quat, Vec3};

fn main() {
    let source = Vec3::new(570.5249, -180.84932, 0.0);
    let source_rotation = Quat::from_array([0.0, 0.0, 0.6075659, 0.7947468]);
    let target = Vec3::new(-576.0742, 105.41407, 0.0);

    let vec_to_target = (target - source).normalize();
    let rotation_to_target = Quat::from_rotation_arc(Vec3::Y, vec_to_target);
    println!("Source rotation: {}", source_rotation);
    println!("Target rotation: {}", rotation_to_target);

    let result = source_rotation.rotate_towards(rotation_to_target, f32::to_radians(30.0));
    println!("Result rotation: {}", result);

    let angle = source_rotation.angle_between(rotation_to_target);
    println!("Angle: {}", angle);

    let shift_vec = result * Vec3::Y;
    let distance = target.distance(source);
    let proj_with_result = source + shift_vec * distance;
    println!("Projection with result: {}", proj_with_result);

    let shift_vec = rotation_to_target * Vec3::Y;
    let distance = target.distance(source);
    let proj_with_target = source + shift_vec * distance;
    println!("Projection with target: {}", proj_with_target);

    let proj_distance = proj_with_result.distance(proj_with_target);
    println!("Projections distance: {}", proj_distance);
}

```
Output:
```
Source rotation: [0, 0, 0.6075659, 0.7947468]
Target rotation: [0, -0, 0.61553717, 0.7881078]
Result rotation: [0, 0, 0.6075659, 0.7947468]
Angle: 0
Projection with result: [-570.7594, 129.35521, 0]
Projection with target: [-576.0742, 105.41408, 0]
Projections distance: 24.52397
```

Patch:
diff --git a/codegen/templates/quat.rs.tera b/codegen/templates/quat.rs.tera
index 017096d..7f9ff9b 100644
--- a/codegen/templates/quat.rs.tera
+++ b/codegen/templates/quat.rs.tera
@@ -729,7 +729,7 @@ impl {{ self_t }} {
         glam_assert!(self.is_normalized() && rhs.is_normalized());
         let angle = self.angle_between(rhs);
         if angle <= 1e-4 {
-            return *self;
+            return rhs;
         }
         let s = (max_angle / angle).clamp(-1.0, 1.0);
         self.slerp(rhs, s)
diff --git a/src/f32/coresimd/quat.rs b/src/f32/coresimd/quat.rs
index 7bcb0b5..f0479a2 100644
--- a/src/f32/coresimd/quat.rs
+++ b/src/f32/coresimd/quat.rs
@@ -585,7 +585,7 @@ impl Quat {
         glam_assert!(self.is_normalized() && rhs.is_normalized());
         let angle = self.angle_between(rhs);
         if angle <= 1e-4 {
-            return *self;
+            return rhs;
         }
         let s = (max_angle / angle).clamp(-1.0, 1.0);
         self.slerp(rhs, s)
diff --git a/src/f32/neon/quat.rs b/src/f32/neon/quat.rs
index 76664ad..8390f2e 100644
--- a/src/f32/neon/quat.rs
+++ b/src/f32/neon/quat.rs
@@ -590,7 +590,7 @@ impl Quat {
         glam_assert!(self.is_normalized() && rhs.is_normalized());
         let angle = self.angle_between(rhs);
         if angle <= 1e-4 {
-            return *self;
+            return rhs;
         }
         let s = (max_angle / angle).clamp(-1.0, 1.0);
         self.slerp(rhs, s)
diff --git a/src/f32/scalar/quat.rs b/src/f32/scalar/quat.rs
index 2cbeb4c..a55c0ec 100644
--- a/src/f32/scalar/quat.rs
+++ b/src/f32/scalar/quat.rs
@@ -597,7 +597,7 @@ impl Quat {
         glam_assert!(self.is_normalized() && rhs.is_normalized());
         let angle = self.angle_between(rhs);
         if angle <= 1e-4 {
-            return *self;
+            return rhs;
         }
         let s = (max_angle / angle).clamp(-1.0, 1.0);
         self.slerp(rhs, s)
diff --git a/src/f32/sse2/quat.rs b/src/f32/sse2/quat.rs
index 634f780..cc477ba 100644
--- a/src/f32/sse2/quat.rs
+++ b/src/f32/sse2/quat.rs
@@ -593,7 +593,7 @@ impl Quat {
         glam_assert!(self.is_normalized() && rhs.is_normalized());
         let angle = self.angle_between(rhs);
         if angle <= 1e-4 {
-            return *self;
+            return rhs;
         }
         let s = (max_angle / angle).clamp(-1.0, 1.0);
         self.slerp(rhs, s)
diff --git a/src/f32/wasm32/quat.rs b/src/f32/wasm32/quat.rs
index 8d0e19f..b7307ba 100644
--- a/src/f32/wasm32/quat.rs
+++ b/src/f32/wasm32/quat.rs
@@ -585,7 +585,7 @@ impl Quat {
         glam_assert!(self.is_normalized() && rhs.is_normalized());
         let angle = self.angle_between(rhs);
         if angle <= 1e-4 {
-            return *self;
+            return rhs;
         }
         let s = (max_angle / angle).clamp(-1.0, 1.0);
         self.slerp(rhs, s)
diff --git a/src/f64/dquat.rs b/src/f64/dquat.rs
index 52b67e5..b6c2d84 100644
--- a/src/f64/dquat.rs
+++ b/src/f64/dquat.rs
@@ -579,7 +579,7 @@ impl DQuat {
         glam_assert!(self.is_normalized() && rhs.is_normalized());
         let angle = self.angle_between(rhs);
         if angle <= 1e-4 {
-            return *self;
+            return rhs;
         }
         let s = (max_angle / angle).clamp(-1.0, 1.0);
         self.slerp(rhs, s)


Test patch:
diff --git a/tests/quat.rs b/tests/quat.rs
index 3c8a090..eebc127 100644
--- a/tests/quat.rs
+++ b/tests/quat.rs
@@ -356,6 +356,11 @@ macro_rules! impl_quat_tests {
             );
             assert_approx_eq!(q2, q0.rotate_towards(q1, -FRAC_PI_2), eps);
             assert_approx_eq!(q2, q0.rotate_towards(q1, -FRAC_PI_2 * 1.5), eps);
+
+            // Small angles
+            let q0 = $quat::from_euler(EulerRot::YXZ, 0.0, 0.0, 0.0);
+            let q1 = $quat::from_euler(EulerRot::YXZ, 1e-4, 0.0, 0.0);
+            assert_eq!(q1, q0.rotate_towards(q1, FRAC_PI_2))
         });
 
         glam_test!(test_fmt, {