This post is a part of a three post series, where I will try to implement popular rigging functionalities by only using maya’s native matrix nodes.
Following the Cult of rig lately, I realized I have been very wasteful in my rigs in terms of constraints. I have always known that they are slower than direct connections and parenting, but then I thought that is the only way to do broken hierarchy rigs. Even though I did matrix math at university, I never used it in maya as I weirdly thought the matrix nodes are broken or limited. There was always the option of writing my own nodes, but since I would like to make it as easy for people to use my rigs, I would rather keep everything in vanilla maya.
Therefore, when Raffaele used the
decomposeMatrix nodes to reparent a transform, I was very pleasantly inspired. Since then, I have tried applying the concept to a couple of other rigging functionalities, such as the twist calculation and rivets and it has been giving me steadily good results. So, in this post we will have a look at how we can use the technique he showed in the stream, to simulate a parent + scale constraint, without the performance overhead of constraints, effectively creating a node based matrix constraint.
There are some limitations with using this approach, though. Some of them are not complex to go around, but the issue is that this adds extra nodes to the graph, which in turn leads to performance overhead and clutter. That being said, constraints add up to the outliner clutter, so I suppose it might be a matter of a preference.
Constraining a joint with
jointOrient values, will not work, as the
jointOrient matrix is applied before the rotation. There is a way to get around this, but it involves creating a number of other nodes, which add some overhead and for me are making it unreasonable to use the setup instead of an orient constraint.
If you want to see how we go around the
jointOrient issue just out of curiosity, have a look at the joint orient section.
Weights and multiple targets
Weights and multiple targets are also not entirely suitable for this approach. Again, it is definitely not impossible, since we can always blend the output values of the matrix decomposition, but that will also involve an additional
blendColors node for each of the transform attributes we need –
scale. And similarly to the previous one, that means extra overhead and more node graph clutter. If there was an easy way to blend matrices with maya’s native nodes, that would be great.
Weirdly, even though the decompose matrix has a
rotateOrder attribute, it does not seem to do anything, so this method will work with only the
xyz rotate order. Last week I received an email from the maya_he3d mailing list, about that issue and it seems like it has been flagged to Autodesk for fixing, which is great.
The construction of such a node based matrix constraint is fairly simple both in terms of nodes and the math. We will be constructing the graph as shown in the Cult of Rig stream, so feel free to have a look at it for a more visual approach. The only addition I will make to it is supporting a maintainOffset functionality. Also, Raffaele talks a lot about math in his other videos as well, so have a look at them, too.
All the math is happening inside the
matrixMult node. Essentially, we are taking the
worldMatrix of a target object and we are converting it to relative space by multiplying by the
parentInverseMatrix of the constrained object. The
decomposeMatrix after that is there to break the matrix into attributes which we could actually connect to a transform –
shear. It would be great if we could directly connect to an input matrix attribute, but that would probably create it’s own set of problems.
That’s the basic node based matrix constraint. How about maintaining the offset, though?
In order to be able to maintain the offset, we need to just calculate it first and then put it in the
multMatrix node before the other two matrices.
The way we calculate the local matrix offset is by multiplying the
worldMatrix of the object by the
worldInverseMatrix of the parent (object relative to). The result is the local matrix offset.
Using the multMatrix node
It is entirely possible to do this using another
matrixMult node, and then doing a
getAttr of the output and set it in the main
matrixMult by doing a
setAttr with the
type flag set to
"matrix". The local
matrixMult is then free to be deleted. The reason we get and set the attribute, instead of connecting it, is that otherwise we create a cycle.
Using the Maya API
What I prefer doing, though, is getting the local offset via the API, as it does not involve creating nodes and then deleting them, which is much nicer when you need to code it. Let’s have a look.
import maya.OpenMaya as om def getDagPath(node=None): sel = om.MSelectionList() sel.add(node) d = om.MDagPath() sel.getDagPath(0, d) return d def getLocalOffset(parent, child): parentWorldMatrix = getDagPath(parent).inclusiveMatrix() childWorldMatrix = getDagPath(child).inclusiveMatrix() return childWorldMatrix * parentWorldMatrix.inverse()
getDagPath function is just there to give us a reference to an
MDagPath instance of the passed object. Then, inside the
getLocalOffset we get the
inclusiveMatrix of the object, which is the full world matrix equivalent to the
worldMatrix attribute. And in the end we return the local offset as an
Then, all we need to do is to set the
multMatrix.matrixIn attribute to our local offset matrix. The way we do that is by using the
() operator which returns the element of the matrix specified by the row and column index. So, we can write it like this.
localOffset = getLocalOffset(parent, child) mc.setAttr("multMatrix1.matrixIn", [localOffset(i, j) for i in range(4) for j in range(4)], type="matrix")
Essentially, we are calculating the difference between the
child objects and we are applying it before the other two matrices in the
multMatrix node in order to implement the
maintainOffset functionality in our own node based matrix constraint.
Lastly, let us have a look at how we can go around the joint orientation issue I mentioned in the Limitations section.
What we need to do is account for the
jointOrient attribute on joints. The difficulty comes from the fact that the
jointOrient is a separate matrix that is applied after the
rotation matrix. That means, that all we need to do is, in the end of our matrix chain rotate by the inverse of the
jointOrient. I tried doing it a couple of times via matrices, but I could not get it to work. Then I resolved to write a node and test how I would do it from within. It is really simple, to do it via the API as all we need to do is use the
rotateBy function of the
MTransformationMatrix class, with the inverse of the
jointOrient attribute taken as a
Then, I thought that this should not be too hard to implement in vanilla maya too, since there are the quaternion nodes as well. And yeah there is, but honestly, I do not think that graph looks nice at all. Have a look.
As you can see, what we do is, we create a quaternion from the joint orientation, then we invert it and apply it to the calculated output matrix of the
multMatrix. The way we apply it is by doing a quaternion product. All we do after that is just convert it to euler and connect it to the rotation of the joint. Bear in mind, the
quatToEuler node supports rotate orders, so it is quite useful.
Of course, you can still use the
maintainOffset functionality with this method. As I said though, comparing this to just an orient constraint it seems like the orient constraint was performing faster every time, so I see no reason of doing this other than keeping the outliner cleaner.
Additionally, I am assuming that there is probably an easier way of doing this, but I could not find it. If you have something in mind, give me a shout.
Using this node based constrain I was able to remove parent, point and orient constraints from my body rig, making it perform much faster than before, and also the outliner is much nicer to look at. Stay tuned for parts 2 and 3 from this matrix series, where I will look at creating a twist calculator and a rivet by using just matrix nodes.