This post is a part of a three post series, where I implement popular rigging functionalities with just using maya’s native matrix nodes.
Rivets are one of those things that blew my mind the first time I learned of them. Honestly, at the time, the ability to stick an object to the deforming components of a geometry seemed almost magical. Although, the more you learn about how geometries work in Maya, the more sense rivets start to make. The stigma around them, though, has always been that they are a bit slow, since they have to wait for the underlying geometry to evaluate and only then can they evaluate as well. And even though that is still the case, it seems that since parallel was introduced the performance has increased significantly.
It is worth trying to simplify and clean rivets up, considering how handy they are for rigging setups like:
– twist distributing ribbons
– bendy/curvy limbs
– sticking objects to geometries after squash and stretch
– sticking controls to geometries
– driving joints sliding on surfaces
When I refer to the classic rivet or the
aimConstraint rivet, it is this one that I am talking about. I have seen it used by many riggers and also lots of lighters as well.
The purpose of this approach is to get rid of the
aimConstraint that is driving the rotation of the rivet. Additionally, I have seen a
pointConstraint used as well, in order to account for the parent inverse matrix, which would also be replaced by this setup. Even though we are stripping constraints, the performance increase is not very large, so the major benefit of the matrix rivet is a cleaner graph.
TL;DR: We are going to plug the information from a
pointOnSurfaceInfo node directly into a
fourByFourMatrix node, in attempt to remove constraints from our rigs.
Disclaimer: Bear in mind, I will be only looking at riveting an object to a NURBS surface. Riveting to poly geo would need to be done through the same old loft setup.
Limitations: Since we are extracting our final transform values using a
decomposeMatrix node, we do not have the option to use any rotation order other than XYZ, as at the moment the
decomposeMatrix node does not support other orders. A way around it, though, is taking the
outputQuat attribute and pluging it into an
quatToEuler node which actually supports different rotate orders.
Difference between follicle and aimConstraint rivet
The locator is riveted using an
aimConstraint. You can see there is a small difference in the rotations of the follicle and the locator. Why is that?
The classical rivet setup connects the
normal attributes of a
pointOnSurface to the
aimConstraint. The third axis is then the cross product of these two. But it seems like the follicle is actually using the
tangentU vector for it’s calculations, since we get this difference between the two setups.
Choosing to plug the
tangentU into the
aimConstraint, instead of
tangentV, results in the same
behaviour as a follicle. To be honest, I am not sure which one would be preferable. In the construction of our matrix rivet, though, we have full control over that.
Why not follicles?
As I already said, in parallel, follicles are fast! Honestly, for most of my riveting needs I wouldn’t mind using a follicle. The one aspect of follicles I really dislike though, is the fact that it operates through a shape node. I understand it was not meant for rigging, and having the objects clearly recognizable both in the outliner and the viewport is important, but in my case it is just adding up to clutter. Ideally, I like avoiding unnecessary DAG nodes, since they only get in the way.
Additionally, have you had a look at the follicle shape node? I mean, there are so many hair related attributes, it is a shame to use it just for
Therefore, if we could use a non-DAG network of simple nodes to do the same job without any added overhead, why should we clutter our rigs?
Constructing the matrix rivet
So, the way matrices work in Maya is that the first three rows of the matrix describe the X, Y and Z axis and the fourth row is the position. Since, this is an oversimplification I would strongly suggest having a look at some matrix math resources and definitely watching the Cult of Rig streams, if you would like to learn more about matrices.
What this means to us, though, is that if we have two vectors and a position we can always construct a matrix out of them, since the cross product of the two vectors will give us a third one. So here is how our matrix construction looks like in the graph.
So, as you can see, we are utilizing the
fourByFourMatrix node to construct a matrix. Additionally, we use the
vectorProduct node set to Cross Product to construct our third axis out of the
normal and the chosen tangent, in this case
tangentV which gives us the same result as using the classic
aimConstraint rivet. If we choose to use the
tangentU instead, we would get the
follicle‘s behaviour. Then, obviously we decompose the matrix and plug it into our riveted transform.
Optionally, similar to the first post in this series, we can use the
multMatrix node to inverse the parent’s transform, if we so need to. What I usually do, though, is parent them underneath a transform that has it’s
inheritTransform attribute turned off, so we can plug the world transforms directly.
It is important to note that in this case we are absolutely sure that the output matrix is orthogonal, since we know that the
normal is perpendicular to both tangents. Thus, crossing it with any of the tangents, will result in a third perpendicular vector.
Skipping the vector product
Initially, when I thought of building rivets like this, I plugged the
tangentV directly from the
pointOnSurfaceInfo to the
fourByFourMatrix. What this means, is that we have a matrix that is not necessarily orthogonal, since the tangents might very well not be perpendicular. This results in a shearing matrix. That being said though, it was still giving me proper results.
Then, I added it to my modular system to test it on a couple of characters and it kept giving me steadily good results – 1 to 1 with the behaviour of a
aimConstraint rivet, depending on the order I plug the tangents in.
What this means, then, is that the
decomposeMatrix node separates all the shearing from the matrix and thus returns the proper rotation as if the matrix is actually orthogonal.
If that is the case, then we can safely skip the
vectorProduct and still have a working rivet, considering we completely disregard the
outputShear attribute of the
Since, I do not understand how that shearing is being extracted, though, I will be keeping an eye on the behaviour of the rivets in my rigs, to see if there is anything dodgy about it. So far, it has proved to be as stable as anything else.
If you are anything like me, you would really like the simplicity of the graph, as we literally are taking care of the full matrix construction ourselves. What is more, there are no constraints, nor follicle shapes in the outliner, which again, I find much nicer to look at.
This matrix series has been loads of fun for me to write, so I will definitely be trying to come up with other interesting functions we could use matrices for.