Saturday, February 3, 2018

unity - Using Quaternions: What can I do with them? (without the maths)


I am a Game Developer and did not study Mathematics. So I only want to use Quaternions as a tool. And to be able to work with 3D rotation, it's necessary to use Quaternions (Or Matrixes, but let's stay at Quaternions here in this Question). I think it's important for many developers to use them. That's why I want to share my knowledge and hopefully fill the holes I have. Now....


So as far as I understood:


A Quaternion can describe 2 things:




  1. The current orientation of a 3d Object.

  2. The rotation transformation a Object could do. (rotationChange)


You can do with a Quaternion:


Multiplications:




  1. Quaternion endOrientation = Quaternion rotationChange * Quaternion currentOrientation;


    So for example: My 3D Object is turned 90° to left - and my rotation I multiply is a rotation 180° to right, in the end my 3D Object 90° is turned to right.





  2. Quaternion rotationChange = Quaternion endRotation * Quaternion.Inverse(startRotation);


    With this you get a rotationChange, which can be applied to another Orientation.




  3. Vector3 endPostion = Quaternion rotationChange * Vector3 currentPosition;


    So for example: My 3D Object is on Position (0,0,0) and my rotation I multiply is a rotation 180° to right, my endposition is something like (0,-50,0) . Inside that Quaternion there is a Axis - and a rotation around that axis. You turn your point around that axis Y Degrees.





  4. Vector3 rotatedOffsetVector = Quaternion rotationChange * Vector3 currentOffsetVector ;


    For example: My start direction is showing UP - (0,1,0), and my rotation I multiply is a rotation 180° to right, my end direction is showing down. (0,-1,0)




Blending (Lerp and Slerp):




  1. Quaternion currentOrientation = Quaternion.Slerp(startOrientation, endOrientation , interpolator)


    if the interpolator is 1 : currentOrientation = endOrientation


    if the interpolator is 0 : currentOrientation = startOrientation



    Slerp interpolates more precise, Lerp interpolates more performant.




My Question(s):


Is everything I explained until now correct?


Is that "all" you can do with Quaternions? (obv. not)


What else can you do with them?


What are the Dot product and the Cross product between 2 Quaternions good for?


Edit:


Updated Question with some answers




Answer



Multiplication


At least in terms of Unity's implementation of Quaternions, the multiplication order described in the question is not correct. This is important because 3D rotation is not commutative.


So, if I want to rotate an object by rotationChange starting from its currentOrientation I'd write it like this:


Quaternion newOrientation = rotationChange * currentOrientation;

(ie. Transformations stack up to the left - same as Unity's matrix convention. The rightmost rotation is applied first / at the "most local" end)


And if I wanted to transform a direction or offset vector by a rotation, I'd write it like this:


Vector3 rotatedOffsetVector = rotationChange * currentOffsetVector;


(Unity will generate a compile error if you do the opposite)


Blending


For most cases you can get away with Lerping rotations. That's because the angle used "under the hood" in a quaternion is half the angle of rotation, making it substantially closer to the linear approximation of Lerp than something like a Matrix (which in general will not Lerp well!). Check out around 40 minutes into this video for more explanation.


The one case when you really need Slerp is when you need consistent speed over time, like interpolating between keyframes on an animation timeline. For cases where you just care that an output is intermediate between two inputs (like blending layers of an animation) then usually Lerp serves quite well.


What Else?


The dot product of two unit quaternions gives the cosine of the angle between them, so you can use the dot product as a measure of similarity if you need to compare rotations. This is a little obscure though, so for more legible code I'd often use Quaternion.Angle(a, b) instead, which more clearly expresses that we're comparing angles, in familiar units (degrees).


These types of convenience methods that Unity provides for Quaternions are super useful. In almost every project I use a this one at least a few times:


Quaternion.LookRotation(Vector3 forward, Vector3 up)

This builds a quaternion that:




  • rotates the local z+ axis to point exactly along the forward vector argument

  • rotates the local y+ axis to point as close as possible to the up vector argument, if provided, or to (0, 1, 0) if omitted


The reason the "up" only gets "as close as possible" is that the system is overdetermined. Facing z+ to forward uses up two degrees of freedom (ie. yaw and pitch) so we have only one degree of freedom remaining (roll).


I find quite often I want something with the opposite exactness properties: I want local y+ to point exactly along up, and local z+ to get as close as possible to forward with the remaining freedom.


This comes up for example when trying to form a camera-relative coordinate frame for movement input: I want my local up direction to stay perpendicular to the floor or inclined surface normal, so my input doesn't try to tunnel the character into the terrain or levitate them off of it.


You might also get this if you want the turret housing of a tank to face a target, without peeling up off of the tank's body when aiming up/down.


We can build our own convenience function to do this, using LookRotation for the heavy lifting:


Quaternion TurretLookRotation(Vector3 approximateForward, Vector3 exactUp)

{
Quaternion rotateZToUp = Quaternion.LookRotation(exactUp, -approximateForward);
Quaternion rotateYToZ = Quaternion.Euler(90f, 0f, 0f);

return rotateZToUp * rotateYToZ;
}

Here we first rotate local y+ to z+, and local z+ to y-.


Then we rotate the new z+ to our up direction (so the net result is local y+ points directly along exactUp), and the new y+ as close as possible to the negated forward direction (so the net result is local z+ points as close as possible along approximateForward)


Another handy convenience method is Quaternion.RotateTowards, which I often use like so:



Quaternion newRotation = Quaternion.RotateTowards(
oldRotation,
targetRotation,
maxDegreesPerSecond * Time.deltaTime
);

This lets us close in on targetRotation at a consistent, controllable speed regardless of framerate - important for rotations that affect the outcome/fairness of gameplay mechanics (like turning a character's movement, or having a turret track-in on the player). Naively Lerping/Slerping in this situation can easily lead to cases where the movement gets snappier at high framerates, impacting game balance. (That's not to say these methods are wrong - there's ways to use them correctly without changing fairness, it just requires care. RotateTowards gives a convenient shortcut that takes care of this for us)


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...