Maya matrix nodes – Part 1: Node based matrix constraint

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 `matrixMult` and `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.

Limitations

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.

Joints

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 – `translate`, `rotate` and `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.

Rotate order

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.

Construction

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 – `translate`, `rotate`, `scale` and `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?

Maintain offset

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.

Calculating offset

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()
``````

The `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 `MMatrix` instance.

Then, all we need to do is to set the `multMatrix.matrixIn[0]` attribute to our local offset matrix. The way we do that is by using the `MMatrix`‘s `()` 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[0]", [localOffset(i, j) for i in range(4) for j in range(4)], type="matrix")
``````

Essentially, we are calculating the difference between the `parent` and `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.

Joint orient

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 `MQuaternion`.

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.

Conclusion

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.

11 Comments on "Maya matrix nodes – Part 1: Node based matrix constraint"

1. Once you can understand Matrices; I think life in general with CG becomes easier. Then you become CG guru.

Reply

2. Hey, could you not blend matricies using weighted add matrix node? My initial tests are promissing

Reply

1. That does sound promising, I’ll give it a go! Is there any performance slowdown? I can’t imagine that being the case, but it’s worth having a look.

Reply

1. Good point regarding performance, I have just done the most basic of tests, so i didn’t go there yet,

Regarding performance, did you happen to test/know the performance benefits of using a matrix mult + decompose node over of a regular constraint? I tested this some years ago (before parallel was a thing) and found very little performance benefit.

Cheers!

Reply

1. Sorry for the late reply.

Just wanted to let you know I added a new post on Blending matrices using the `wtAddMatrix` node as you suggested. I had a look and it seemed to work perfectly in that specific use case. Nice one, thanks for sharing!

As for the performance, you will notice in that new post I did a performance test to see how the setup compares to a `parentConstraint`. The results seem to be that the matrix constraint setup is significantly faster. About 8fps per 200 instances of the setup, which if you imagine multiple rigs in the scene you can easily see the benefits. Bear in mind that is with the `wtAddMatrix` nodes in there, so if you have just one target the increase would be even slightly larger.

That being said, just as you did, I also found that before parallel the increase was quite small. Not only on this setup but on other things like rivets and follicles as well. I guess we should be happy parallel is here to stay.

Reply

3. I might of found a simpler method for the joint orient, I’ve only done a quick test but so far it appears to be working.
Compose a matrix from the child object but use joint orient instead of rotate, then connecting that into a inverse matrix and do the same getAttr / setAttr method to add it to the mult matrix that you used in your offset section.

Reply

1. Really cool and looks very promising! The reason I avoided doing something like this is that I wanted to do a `rotateBy` function similar to how I had done in with the API previously. Honestly, though, I don’t see any reason why not do it as you are suggesting.

I suppose the only real difference is that when constructing the matrix you need the position of the child object, which means that we can’t leave this connection live as it is going to cycle. So in that case, the matrix setup will need to be updated everytime the `jointOrient` is changed.

That being said, I can’t imagine people messing about with the joint orientation after it has been constrained, so that would not be an issue at all.

Nice one!

Reply

4. Thanks for the info!

Any idea how to deal with rotate pivots in this setup? I figured out how to account for the target’s rotate pivot (just another offset matrix at the start of the matrix mult). But the constrained node’s rotate pivot doesn’t appear to be so simple.

Reply

1. Yeah, pivots can be tricky. I have to say that I never use this setup when I work with pivots. I also have to say, though, that I very rarely have to work with pivots and even then it can be avoided.

That being said, I gave it a quick go to see how we can account for these pivots and it seems like the following setup works, but obviously, it is a very simplified one, so it would be great if you can let me know if it works for you.

Here’s the graph.

So basically, we are accounting for the pivot in the beginning and then we multiply by the inverse of the pivot in the end, as otherwise we get a double transformation.

Here’s how it looks like.

Hopefully that works for you, but it might be a complete mess.. haven’t had my coffee, yet! ðŸ˜€

Reply