From 2e8df10e6e66dcb2f75c1ef15a034d860159a9e4 Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Sun, 31 Jan 2021 10:03:35 -0800 Subject: [PATCH] Quaternion slerp: ensure "short path" when falling back to lerp. I noticed that our slerp function sometimes produces a jolt in animation, but only when the time delta is very small, and only when the two operands have completely opposing signs. For example, let's say you are slerping from <0.76, 0.39, 0.51, 0.19> to <-0.72, -0.45, -0.49, -0.17>. These quats are actually quite near to each other because the total negation of the second quat is similar to the first quat. We were already doing the short path check in the proper slerp path, but not when falling back to lerp due to a small angle. --- libs/math/include/math/TQuatHelpers.h | 2 +- libs/math/tests/test_quat.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/libs/math/include/math/TQuatHelpers.h b/libs/math/include/math/TQuatHelpers.h index 01f86b7329..184420b4db 100644 --- a/libs/math/include/math/TQuatHelpers.h +++ b/libs/math/include/math/TQuatHelpers.h @@ -251,7 +251,7 @@ public: static constexpr T value_eps = T(10) * std::numeric_limits::epsilon(); // Prevent blowing up when slerping between two quaternions that are very near each other. if ((T(1) - absd) < value_eps) { - return normalize(lerp(p, q, t)); + return normalize(lerp(d < 0 ? -p : p, q, t)); } const T npq = std::sqrt(dot(p, p) * dot(q, q)); // ||p|| * ||q|| const T a = std::acos(filament::math::clamp(absd / npq, T(-1), T(1))); diff --git a/libs/math/tests/test_quat.cpp b/libs/math/tests/test_quat.cpp index 8d066e6779..6a5849875e 100644 --- a/libs/math/tests/test_quat.cpp +++ b/libs/math/tests/test_quat.cpp @@ -273,6 +273,20 @@ TEST_F(QuatTest, ArithmeticFunc) { qs = slerp(qa, qb, 0.5); EXPECT_NEAR(qs[3], -0.92, 0.1); EXPECT_NEAR(qs[2], +0.38, 0.1); + + // Create two quats that are near to each other, but with opposite signs. + qa = { 0.76, 0.39, 0.51, 0.19}; + qb = {-0.759, -0.385, -0.50, -0.19}; + qs = slerp(qa, qb, 0.5); + + // The rotation angle produced by v * slerp(A, B, .5) should be between the rotation angles + // produced by (v * A) and (v * B). + double3 v(0, 0, 1); + double3 va = qa * v; + double3 vb = qb * v; + double3 vs = qs * v; + EXPECT_LT(dot(v, va), dot(v, vs)); + EXPECT_LT(dot(v, vs), dot(v, vb)); } TEST_F(QuatTest, MultiplicationExhaustive) {