This is probably the last post I’ll do on matrices (unless anyone has any ideas on other posts that’d be useful). But I figured I’d do as I initially promised and go through writing code to determine euler angles for a simple aimAt/lookAt function. The code will only construct the rotation matrix and return euler angles, it won’t be plugin code. Writing an actual maya node is kinda dull, and probably done really well in existing books and tutorials on the web… So this post will focus entirely on the problem of deriving the rotation itself.
However, lets start with a quick recap. So remember the rotation matrix contains the three basis: the rotated x, y and z axes. In maya parlance these are called the object space axes. Now to construct our aiming rotation matrix we need to do two main things – rotate the object to aim at the target, and then roll the object around this aim axis appropriately. Defining roll appropriately is a little tricky – the typical maya aim constraint provides a bunch of pretty good options such as static “vector”, “object up” or most usefully (in my opinion) “object rotation”. So I’m going to write code to use the object rotation method for roll determination. Once you understand how to do the object rotation method however, the other methods are really easy to implement.
One last thing – for the sake of simplicity we’re going to make the following assumptions. The object will aim with its X axis, and use Y as its up axis, and we’ll use the Z axis from the up object. Again, once you understand how the code works changing these hardcoded assumptions is pretty easy, so we’re starting out by keeping it simple.
Anyway, lets dig in. So first, lets discover the aim vector. This is basically the vector between the aim object and the target.
aimVector = getWorldPos( tgtObj ) - getWorldPos( aimObj ) aimVector = aimVector.normalize()
So this gives us the vector that we want to aim along. Now in the simple case, we can say that the aim axis is the X axis, and then this aimVector becomes the X basis vector for the aim rotation matrix.
Now to deal with roll – ie the up vector. Lets assume for now that we’re using Y as the up axis, and we’re using “object rotation” as the up axis determination method, using the Z axis from the up object. The first step is to get the Z axis from the world matrix of the up object. Remember, this is just grabbing the third row out of the matrix assuming its not scaled. If it is scaled you’ll need to decompose the the matrix into rotation and scale and grab the third row of the rotation matrix.
Once we have this vector, we’re not quite done. This vector we just extracted is almost certainly not going to be orthogonal to the aim vector we found above (aimX). We need to make it orthogonal. Remember, rotation matrices contain three mutually orthogonal basis vectors. The easiest way (that I know – there is a good chance I’m wrong though) is to subtract out the part of the aimX vector that is in the direction of the aim vector. Sounds confusing, but its easy – it looks like this:
aimX = aimVector #define the aimVector from above as our aimX upObjWorldMatrix = getWorldMatrix( upObj ) upVector = Vector( upObjWorldMatrix[ 2 ][ :3 ] ) #grab the z axis from the up object aimComponentInUp = aimX * upVector.dot( aimX ) upVector = upVector - aimComponentInUp aimY = upVector
Whats going on here? Remember as we stated above, what this is doing is removing any component of the aim vector from the up vector. In this case we’re grabbing the up vector from another object so its unlikely to be orthogonal to our aim vector. If we plug a non-orthogonal vectors into our aim matrix then we’ll have to decompose (or the euler angle computation will be incorrect) – which is an expensive operation. So instead we’ll do some quick vector math to ensure orthogonality before constructing the rotation matrix.
Back to the code – aimComponentInUp here is the component of aimX in the direction of the proposed up vector. If they are orthogonal already, then this vector will be the zero vector, but if the vector is parallel, then the result of the subtraction will be the zero vector and the roll rotation will be impossible to determine. This is the case where flipping occurs.
The last step now is to figure out the Z axis on the aim node. Now we already have the X axis and the Y axis, and the basis vectors are mutually orthogonal, which means we can find the Z axis by taking the cross product of them. The only thing we need to be aware of here is the order the cross product is done in. Now maya is a right handed system so we need to make sure the cross product is done in the correct order. In this case we want to do X cross Y.
aimZ = aimX.cross( aimY )
Now lets put it all together.
from maya.cmds import * from vectors import Matrix, Vector def getWorldMatrix( obj ): return Matrix( xform( obj, q=True, ws=True, matrix=True ) ) def getWorldPos( obj ): return Vector( getWorldMatrix( obj )[ :3 ] ) def getAimRotations( aimObj, tgtObj, upObj ): worldPosTgt = getWorldPos( tgtObj ) worldPosAim = getWorldPos( aimObj ) aimVector = worldPosTgt - worldPosAim aimX = aimVector.normalize() upObjWorldMatrix = getWorldMatrix( upObj ) upVector = Vector( upObjWorldMatrix[ 2 ][ :3 ] ) aimComponentInUp = aimX * upVector.dot( aimX ) upVector = upVector - aimComponentInUp aimY = upVector aimZ = aimX.cross( aimY ).normalize() aimRotationMatrix = Matrix.Zero( 3 ) aimRotationMatrix.setRow( 0, aimX ) aimRotationMatrix.setRow( 1, aimY ) aimRotationMatrix.setRow( 2, aimZ ) return aimRotationMatrix.ToEulerXYZ( True )
And thats about it. It really is pretty straight forward. Now naturally the code gets a little uglier if you have to deal with user defined aim and up axes (or arbitrary vectors like maya’s native aim constraint), but if you “grok” the above, then adding functionality for user defined aims and ups is pretty easy.
So thats pretty much it. Matrices are super useful to understand – whether you’re a technical artist or rigger – even as a rigger you’ll find them useful to understand. So if you found this interesting and haven’t read the other posts in the series – check em out here.
This post is public domain
This entry was posted on Wednesday, November 10th, 2010 at 14:42 and is filed under main. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.