Average Quaternions for Carried Items


I wanted the item you carry around to always follow the player camera, but not perfectly. I wanted it to fall behind and catch up, have its own momentum. Parenting it to the camera would be easy, but it would be stiff. Keeping it independent and constantly Lerping to catch up works, and is what I did in the original, but this introduces a lot of unpleasant jitter. This is a conundrum I've come to know as "gun-lag" (in a typical first-person game, the item you're carrying would be a gun).

Old interpolation model. Note the jitter of the key in your hand.

New average quaternion solution. Smoother!

The solution I came up with to improve on the original is averaging the Quaternions. A quaternion, for simplicity's sake, is a rotation state in world space. The current Quaternion is where my object is now (probably lagging behind the camera). And the goal Quaternion is wherever the camera is now (a perfect match between camera and object). Slerping (spherically interpolating) between the current rotation and a goal rotation works, but is jittery, due to imperfect slicing of time and sampling of mouse input. So what if I capture several past orientations of the camera, average them out, then slerp between current and the average instead? 

I keep a running array of 6 stored Quaternions (storedQs[]), I average them, apply the final slerp, and then update the array with a new camera rotation every frame. (Because I couldn't find a legit way to average quaternions, I figured I could just Slerp them one after another. It's a lot of heavy math, and maybe I can optimize it better, but it seems to work for now). Here is the code, which is called every LateUpdate:

    void AvQuatSolution()
    {
        float tdt = Time.deltaTime * snappiness;

        // find average
        Quaternion avQuat = storedQs[0];
        for (int i = 0; i < storedQs.Length - 1; i++) {
            avQuat = Quaternion.Slerp(avQuat, storedQs[i + 1], frontWeight);
        }

        // apply the average to our inventory holder
        transform.rotation = Quaternion.Slerp(transform.rotation, avQuat, tdt);
        
        // shift array for next frame
        for (int i = storedQs.Length - 1; i > 0; i--) {
            storedQs[i] = storedQs[i - 1];
        }
        storedQs[0] = cam.rotation;
    }

Comments

Log in with itch.io to leave a comment.

(+1)

Quaternions are in a sense just a 4D vector, so you can perfectly calculate the average component wise. Once you renormalize, it should be a rotation again. Of course, if the different quaternions are very different (say almost opposite of each other), the numerical errors are big, but the same is true for every way to calculate an average.
But keep in minde that my knowledge of quaternions for 3D rotations is 30 years old and purely theoretical, so it's just a thought, not an advise from a pro in that area.

(1 edit)

Thank you! So add up the 4 components separately, divide by 6, and renormalize back into a vector4? I'll try that and see what happens!

That could help me get rid of that costly 6x Slerp loop.

(+1)

Yes. Because you need to renormalize anyway, you can omit the divide by 6.  

Have fun and I have high hopes for the game, the demo was already fantastic!