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.
This commit is contained in:
Philip Rideout
2021-01-31 10:03:35 -08:00
parent 2f9c310127
commit 2e8df10e6e
2 changed files with 15 additions and 1 deletions

View File

@@ -251,7 +251,7 @@ public:
static constexpr T value_eps = T(10) * std::numeric_limits<T>::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)));

View File

@@ -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) {