Lerping a Quaternion the long way around

So we all know that Quanternion.Lerp() moves from one rotation to the other over the shortest possible distance.

I would like to know if there is a way to make the Lerp go in the opposite direction.

My initial impulse was to find the rotation at the “halfway rotation” in the lerp (where t = 0.5) between two rotations and then reverse the rotation to face the opposite direction. With this new rotation, I would rotate from my start to that, and then from there to my end, but I have absolutely no idea how to “reverse” the angle of the rotation.

Is what I’m asking even possible?

I think those solutions are weird or at least not very “quaternion” friendly.

I have written the lerp and slerp myself, with a flag to indicate whether you want to force the short way or not. I am pretty sure that Unity checks that to invert the rotation. With this code, you can indicate if you want that behaviour.

I have checked myself and it seems it works ok, but check yourself just in case:

public static class QuaternionExtension
{
    public static Quaternion Lerp(Quaternion p, Quaternion q, float t, bool shortWay)
    {
        if (shortWay)
        {
            float dot = Quaternion.Dot(p, q);
            if (dot < 0.0f)
                return Lerp(ScalarMultiply(p, -1.0f), q, t, true);
        }

        Quaternion r = Quaternion.identity;
        r.x = p.x * (1f - t) + q.x * (t);
        r.y = p.y * (1f - t) + q.y * (t);
        r.z = p.z * (1f - t) + q.z * (t);
        r.w = p.w * (1f - t) + q.w * (t);
        return r;
    }

    public static Quaternion Slerp(Quaternion p, Quaternion q, float t, bool shortWay)
    {
        float dot = Quaternion.Dot(p, q);
        if (shortWay)
        {
            if (dot < 0.0f)
                return Slerp(ScalarMultiply(p, -1.0f), q, t, true);
        }

        float angle = Mathf.Acos(dot);
        Quaternion first = ScalarMultiply(p, Mathf.Sin((1f - t) * angle));
        Quaternion second = ScalarMultiply(q, Mathf.Sin((t) * angle));
        float division = 1f / Mathf.Sin(angle);
        return ScalarMultiply(Add(first, second), division);
    }


    public static Quaternion ScalarMultiply(Quaternion input, float scalar)
    {
        return new Quaternion(input.x * scalar, input.y * scalar, input.z * scalar, input.w * scalar);
    }

    public static Quaternion Add(Quaternion p, Quaternion q)
    {
        return new Quaternion(p.x + q.x, p.y + q.y, p.z + q.z, p.w + q.w);
    }
}

Okay, I got this to do what I wanted. It might be a bit more annoying to pull off, but it provides that much more customization to my sword swings, so that works out.

What I did is add a third rotation for the midpoint along my path. Then, if I’ve set up the slash to use that mid rotation, I fire off this bit of code (understand that “lerpTime” is a 0 - 1 float for the lerp).

if (0.5f >= lerpTime)
    relativeRotation = Quaternion.Lerp(startRotation, midRotation, lerpTime * 2);

else
    relativeRotation = Quaternion.Lerp(midRotation, endRotation, (lerpTime - 0.5f) * 2);

Thus, so long as the difference between startRotation and midRotation and midRotation and endRotation are each less than 180 degrees from each other, the movement looks correct. With a sword slash, this also allows me to incorporate sweeping motions and little flicks that might have otherwise been very difficult to pull off.

I hope this is useful to other folks beside me. I know doing sword slashes through code isn’t exactly a broad use case.

It certainly is. And there is probably also a simpler/better way to do it, but since I’m not a Master of Quaternion, I’d suggest this:

  • find the Quaternion covered by your lerp with Quaternion.FromToRotation(A,B)
  • convert that to Angle/Axis representation with Quaternion.ToAngleAxis(angle,axis)
  • what you now want is to rotate around that axis by angle-360, for example with the help of transform.RotateAround()

This works for me:

Quaternion.SlerpUnclamped(a, b, -t);

(Note the negative t value)