Maya matrix nodes – Part 3: Matrix rivet

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

and others.

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

Matrix rivet - follicle and classical rivet differences

Matrix rivet - follicle and classic rivet graph

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 tangentV and 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 parameterU and parameterV.

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.

Matrix rivet - constructing the matrix

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 normal, tangentU and 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.

Matrix rivet - skipping the vectorProduct

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 follicle or 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 decomposeMatrix.

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.

8 Comments on "Maya matrix nodes – Part 3: Matrix rivet"

  1. Very interesting! How does this compare in speed to the follicle? I’m assuming you wrote this after doing that speed test between the aim constraint rivet and the follicle…


    1. Thanks! Yes, I did compare them, but the performance was just about the same (the variations were very small and going both ways over a few tests), which means the matrix rivet is significantly faster than the aimConstraint one. Looks like the follicle is really well optimized, which is great! The only benefits we get from this setup compared to the follicle is we get to choose which tangent we want to use as an up vector and also we have less DAG nodes in the outliner.


      1. Thanks for the reply (and for sharing all these techniques, for that matter). So, to clarify – the matrix rivet and follicle were basically the same in speed? Or at least, one or the other performed slightly faster as you did various comparisons without any consistently faster method… Did I read that correctly? Was this testing the matrix constraint with or without the added vector product? I can definitely see the benefit of being able to choose u OR v as an up vector depending on your needs (and less dag nodes is nice too).

        One drawback I’ve found in my testing is that if you want to make something slide along the surface and you’re driving the param u and v values on the pointOnSurfaceInfo node, as soon as you go outside the uv range of your surface, the riveted node snaps back to origin. I threw a clamp node in there between my driver and param u/v attrs to resolve this, but it’s a little extra step I wish I didn’t have to do… This doesn’t happen with a follicle – it looks like it automatically clamps the uv values for you. But, if you’re not sliding stuff around the surfaces, this is moot!


        1. Yes, that’s correct. Didn’t see any noticeable speed difference between the two. And the tests were done without the vector product.

          About the sliding, I know what you mean, it could be a bit frustrating. What I do usually, is completely ignore that and leave it to the animators to not break it, just so we don’t have to add an extra step and also if they are pushing it to the ends of the nurbs surfaces they should expect it to break. That’s not a great solution, though, so what I could suggest is writing a node for these sliding setups. It can be faster than a follicle and have the choice for tangents.


          1. Haha, ignore it and trust the animators to not break it – awesome (love it!).

            Yeah, I tend to make a surface larger than I think an animator would slide something along it anyway to prevent that type of thing from happening (and trust them to not break it). The locator just has that “extra cushion” where it stops at the edge of the surface instead of popping to the origin.

            Of course, a home-made plugin node could do the trick too! -thanks again!

  2. Quick question, I was getting alot of “vibrations” from the locators when moving the nurbsPlane CV’s, it stopped happening once i changed the Vector Product Operation to Cross Product, was it supposed to be like that from the start, or am I missing something?

    Really like the results with the driven joints for this!.


    1. Hi! Yes, the vector product needs to be set to cross product. That way we are getting a third vector perpendicular to the two inputs.


Leave a Reply

Your email address will not be published. Required fields are marked *