::Matrices In The Wild::

October 4th, 2010 by hamish download the zooToolBox

Ok so up till now its been kinda academic… Matrix algebra, inverses, solving for variables, WTH?! Lets take a post to chill out a bit from the math nerdism going on and actually apply some of what we’ve learned so far.

As I mentioned earlier, I’ve wanted to know how to work with matrices pretty much ever since I started out doing tech art type stuff, but I’ve often been able to find work arounds which have required way less effort on my part, so its always taken a back seat to work arounds.

But the one thing that has kept coming up for me is the concept of mirroring – not mirroring in the sense of scaleX *= -1, but less easily definable things like when you mirror a skeleton in maya for example.

Anyway so this problem came up reasonably recently.  I wanted a node that would take one half of a skeleton, and drive the other half in a mirrored fashion. Why? I’ll go over it at the end of the post. For now lets get our hands dirty.

So you want to mirror your skeleton. So the first thing I did was jump into maya, build a skeleton and mirror one side. Now I use (like I expect most people do) the “behavior” option when mirroring, because its seems to make sense to me and animators. Then I turned on the local axis display on the joint I just mirrored, and the mirrored joint and compared the local axes to try and figure out what was being done to them. Remember, rotations are just another way of describing a transforms local axes – or its basis vectors – so by understanding what happens to the basis vectors, you understand whats happening to the rotations.

It turns out its really simple. Basically for each basis vector, all values get negated (multiplied by -1) except the value of the axis you’re mirroring across. So say you’re mirroring across the X axis – that means the mirrored X basis vector has its Y and Z values negated, and similarly for the Y and Z basis vectors.

Enough talking – lets look at the code, it should make more sense.

inWorldMatrix = Matrix( getAttr( 'obj.worldMatrix' ) )

#extract the basis vectors from the matrix
R = inWorldMatrix.getRotationMatrix()  #gets the 3x3 rot matrix and factors out scale
x, y, z = R  #extract basis vectors

#mirror the rotation axes
axis = Axis( 0 )  #axis 0 is the X axis
idxA, idxB = axis.otherAxes()  #otherAxes returns the other 2 axes, Y and Z

x[ idxA ] = -x[ idxA ]  #negate the values of the other two axes
x[ idxB ] = -x[ idxB ]

y[ idxA ] = -y[ idxA ]
y[ idxB ] = -y[ idxB ]

z[ idxA ] = -z[ idxA ]
z[ idxB ] = -z[ idxB ]

#construct the mirrored rotation matrix
mirroredMatrix = Matrix( x + y + z, 3 )

#now put the rotation matrix in the space of the target object
tgtParentMatrixInv = Matrix( getAttr( 'tgt.parentInverseMatrix' ) )
matInv = Matrix( [ tgtParentMatrixInv[0][0], tgtParentMatrixInv[0][1], tgtParentMatrixInv[0][2],
                   tgtParentMatrixInv[1][0], tgtParentMatrixInv[1][1], tgtParentMatrixInv[1][2],
                   tgtParentMatrixInv[2][0], tgtParentMatrixInv[2][1], tgtParentMatrixInv[2][2] ], 3 )

#put the rotation in the space of the target's parent
mirroredMatrix = mirroredMatrix * matInv

#if there is a joint orient, make sure to compensate for it
tgtJo = getAttr( 'tgt.jo' )[0]

jo = Matrix.FromEulerXYZ( *tgtJo )
joInv = jo.inverse()
mirroredMatrix = mirroredMatrix * joInv

#grab euler values
eulerXYZ = mirroredMatrix.ToEulerXYZ( degrees=True )
setAttr( 'tgt.r', *eulerXYZ )

I think thats right. So I took this code out of the scripted node I wrote to do this in maya, which you can check out here.  As you can see, on around line 12-ish the axes are getting negated.  The rest of the code is simply making sure the transformation is in the appropriate space.  The mirror happens on the world matrix, so once the world matrix axes have been mirrored, we need to convert the matrix back into local space, so we can get rotation values so we can set the attributes.

If you download the plugin, make two “things” and run the command: rotationMirror; Then you should see the second thing you had selected mirror the rotation and position of the first thing you selected. There is a bit of functionality I still need to add to the rotationMirror command, but for now it simply lets you query and edit the two attributes on the node you see in the channelBox.

Anyway, I’m not sure how clear this has all been so let me know.

So what is this plugin used for?  Well, I’ve long wanted to overhaul CST and a bit over a year ago I started doing exactly this.  Its pretty mature at this point, but not quite in a state to be publicly released yet (it still has a bunch of Valve specific code entangled up with the rest of it).  I hope I’ll be able to release it sometime in the near future, but no promises – plus its no where near as “sexy” as that recent rigging tool thats been doing the rounds (I can’t find a link…).  The main focus was on making it easy for non technical people to use it, as well as making it easy to extend/change which is important when having to support multiple projects.

Part of the tool is to make building the skeletons easy and bullet proof, and given that most skeletons are symmetrical, being able to press a button and have the skeleton just magically mirror one side to the other is really helpful.  Initially I tried doing this by just connecting some values and reversing others.  Provided the user didn’t do things like change rotation/joint orient values, it worked fine.  But when it failed, users had no idea why and became frustrated, and the way the alignment tools worked it was easy to break.  Writing this mirror node made all these problems go away, it made users happier, it made me happier and it made everyone more productive.  Win.

This is post 4 in the matrix series.

UPDATE: if you want to use the plugin you’ll need to download a few other scripts:


This post is public domain

This entry was posted on Monday, October 4th, 2010 at 19:45 and is filed under main, tutorials. 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.