Geometry module: Optimize AngleAxis, EulerAngles

libeigen/eigen!2392

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Geometry/Quaternion.h b/Eigen/src/Geometry/Quaternion.h
index 3dff591..b409833 100644
--- a/Eigen/src/Geometry/Quaternion.h
+++ b/Eigen/src/Geometry/Quaternion.h
@@ -821,12 +821,8 @@
 EIGEN_DEVICE_FUNC inline typename internal::traits<Derived>::Scalar QuaternionBase<Derived>::angularDistance(
     const QuaternionBase<OtherDerived>& other) const {
   EIGEN_USING_STD(atan2)
-  // For unit quaternions: dot = cos(theta/2), so theta = 2*atan2(sin(theta/2), |cos(theta/2)|).
-  // Using atan2 instead of acos for numerical accuracy at small angles.
-  Scalar d = numext::abs(this->dot(other));
-  // Clamp to handle non-unit quaternions gracefully.
-  if (d >= Scalar(1)) return Scalar(0);
-  return Scalar(2) * atan2(numext::sqrt(Scalar(1) - d * d), d);
+  Quaternion<Scalar> d = (*this) * other.conjugate();
+  return Scalar(2) * atan2(d.vec().norm(), numext::abs(d.w()));
 }
 
 /** \returns the spherical linear interpolation between the two quaternions
diff --git a/test/geo_quaternion.cpp b/test/geo_quaternion.cpp
index 27c6881..897c5e9 100644
--- a/test/geo_quaternion.cpp
+++ b/test/geo_quaternion.cpp
@@ -104,6 +104,15 @@
     VERIFY_IS_MUCH_SMALLER_THAN(abs(q1.angularDistance(q2) - refangle), Scalar(1));
   }
 
+  // angular distance with non-unit quaternions (scale-invariance)
+  {
+    Quaternionx qunit(AngleAxisx(Scalar(EIGEN_PI / 2), Vector3::UnitX()));
+    Quaternionx qscaled;
+    qscaled.coeffs() = qunit.coeffs() * Scalar(2);
+    VERIFY_IS_APPROX(qscaled.angularDistance(Quaternionx::Identity()), Scalar(EIGEN_PI / 2));
+    VERIFY_IS_APPROX(Quaternionx::Identity().angularDistance(qscaled), Scalar(EIGEN_PI / 2));
+  }
+
   // Action on vector by the q v q* formula
   VERIFY_IS_APPROX(q1 * v2, (q1 * Quaternionx(Scalar(0), v2) * q1.inverse()).vec());
   VERIFY_IS_APPROX(q1.inverse() * v2, (q1.inverse() * Quaternionx(Scalar(0), v2) * q1).vec());