1
\$\begingroup\$

I am working with Unity (C#) and was looking for an implementation of Slerp and possibly Lerp that allow overshoot (progress not strictly limited to 0..1 range). I want to do some tweening animation using an elastic ease-out function. My ease-out function returns values slightly outside the 0..1 range in order to get an oscillating-around-the-target effect. The problem is that Unity's Quaternion Lerp and Slerp functions appear to clamp the progress input in the 0..1 range.

Implementation code in any language would be helpful. I don't know enough about Quaternion math to write this myself.

\$\endgroup\$
  • \$\begingroup\$ Have tried applying your elastic easing function to the euler angles? \$\endgroup\$ – Kelly Thomas Apr 11 '15 at 11:39
2
\$\begingroup\$

Here’s a little example. Suppose you slerp q0 and q1 with a value of t = 0.2 and a value of t = 0.4. This gives you a quaternion s(0.2) and a quaternion s(0.4). Quaternion multiplication also gives us the following:

s(0.2) = q0 * inverse(q0) * s(0.2)
              '——————— K ————————'

So we have a value K which, when right-multiplied with q0, gives us s(0.2). Now intuitively we can guess that multiplying twice will give us s(0.4):

s(0.4) = q0 * inverse(q0) * s(0.2) * inverse(q0) * s(0.2)
              '——————— K ————————'   '——————— K ————————'

Simplifying:

s(0.4) = s(0.2) * inverse(q0) * s(0.2)

We can use this technique to slerp with values greater than 1 by just dividing by two. Which brings us to the following pseudocode:

quat slerp_generic(quat q0, quat q1, float t)
{
    // If t is too large, divide it by two recursively
    if (t > 1.0)
    {
        quat tmp = slerp_generic(q0, q1, t / 2);
        return tmp * inverse(q0) * tmp;
    }

    // It’s easier to handle negative t this way
    if (t < 0.0)
        return slerp_generic(q1, q0, 1.0 - t);

    return slerp(q0, q1, t);
}

Edit: the above code works great for slerp; for lerp however you’re going to get discontinuities in the derivative which you may not want. This code uses a similar technique and will work better for lerp:

quat lerp_generic(quat q0, quat q1, float t)
{
    // If t is too large, subtract 1.0 from it recursively
    if (t > 1.0)
    {
        quat tmp = lerp_generic(q0, q1, t - 1.0);
        return tmp * inverse(q0) * q1;
    }

    // It’s easier to handle negative t this way
    if (t < 0.0)
        return lerp_generic(q1, q0, 1.0 - t);

    return lerp(q0, q1, t);
}
\$\endgroup\$
  • \$\begingroup\$ This is a nice little gem. \$\endgroup\$ – teodron Jun 11 '15 at 20:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.