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\$
1
  • \$\begingroup\$ Have tried applying your elastic easing function to the euler angles? \$\endgroup\$ Apr 11, 2015 at 11:39

1 Answer 1

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\$
1
  • \$\begingroup\$ This is a nice little gem. \$\endgroup\$
    – teodron
    Jun 11, 2015 at 20:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .