Hello there! Last year I was battling with this issue, I have always used ribbons for all my setups that require bendy bones. Once an animator told me something related with them. In some axis, on an extreme position, the ribbon breaks and sometimes we don't need to control the twist.
Rotation in the secondary axis, no issues
Rotation in the up axis, flipping
I've been looking for an answer, but I've been told to only use SplineIK or Motion Path or a custom node. Therefore, I wanted to do a simple setup to mimic the behaviour of the SplineIK without the issues that it has. Besides, I got interested in doing rigs setup with less DAG nodes. I came across with the way how Vasil Shotarov does the rivet on NURBS Surfaces without any DAG node.
I've been doing several tests related with this approach. I'm going to explain each method to use a curve to drive transforms, without using custom nodes, and explain its advantages and disadvantages.
SplineIK:
- Customizable a bit tricky ✔
- It has a built-in twist setup ✔
- It's required to know the primary and secondary axis ✖
- It's unstable when you stretch ✖
- Require lots of dag nodes and joints in order to work properly ✖
- Can keep the same distance between transforms ✔
- Very heavy for parallel evaluation ✖✖
MotionPath:
- Very simple and straightforward ✔
- It needs a separate twist setup ✖
- It's required to know the primary and secondary axis ✖
- It's stable when you stretch it ✔
- It has issues related with flipping in the up axis✖
- It doesn't require any dag nodes, except for the worldUpObject ✔
- Can keep the same distance between transforms ✔
- Light for parallel evaluation ✔
IkHandle + PointOnCurveInfo + Extend Curve:
- Very tricky setup ✖✖
- It needs a separate twist setup ✖
- Doesn't required to know any axis ✔✔
- It's stable when you stretch it ✔
- Sometimes it will calculate the rotation incorrectly, I haven't tested it enough ✖
- Require lots of dag nodes and joints in order to work properly ✖
- Cannot keep the same distance between transforms ✖
- Heavy for parallel evaluation ✖
As you can see here each method has its differences, you will need to outweigh each method and use one of them depending on your necessities. However, I recently discovered a way to solve the flipping, instability and it don't require a bunch of dag nodes. This method I'm going to call it Curve Rivet. I'm going to explain how to set it up.
Curve Rivet:
- Tricky setup ✖
- It needs a separate twist setup ✖
- It's stable when you stretch it ✔
- Doesn't have any issues related with flipping ✔✔
- It doesn't require any dag nodes, except for the worldUpObject ✔
- Can keep the same distance between transforms ✔
- Light for parallel evaluation ✔
I'm showing the graph of the setup, which is quite straightforward. There are a few things to take into account:
- The curve driver must be normalized.
- The orientation of the worldUpObj doesn't matter, because it's been compensated.
- The final composeMatrix should use quaternions not euler.
The whole graph
- As it's shown, we need to know the aimQuat and aimOffsetMatrix. There are several ways to obtain them. I'll show you the method that I use:
import math
import maya.api.OpenMaya as om2
import maya.cmds as mc
"""
tanVector is pointOnCurveInfo.tangent or pma.output3d
objUp is a transform to control the twist
"""
aimVector = [1,0,0]
#How to obtain aimQuat
eulerRot = mc.angleBetween(vector1=aimVector,
vector2=tanVector,
constructionHistory=False,euler=1)
radRot = map(math.radians,eulerRot)
mEuler = om2.MEulerRotation(radRot,order=0)
aimQuat = mEuler.asQuaternion()
#How to obtain aimOffsetMatrix
aimMMatrix = aimQuat.asMatrix()
objUpMMatrix = om2.MMatrix(mc.getAttr(objUp + '.worldMatrix[0]'))
aimOffsetMatrix = aimMatrix*objUpMMatrix.inverse()
As you can see is working fine, but you may know that the PoCI (pointOnCurveInfo) cannot keep the same distance between the transforms while we move the curve. We can use a motionPath instead of it. This node has a build-in attribute called "Parametric length", which will do what the PoCI can't. The only drawback of the motionPath is the tangent vector is not obtainable from it. For this reason, I will explain the following:
Fake Tangents:
As I mentioned before we can use a motionPath, but it's mandatory to have a tangent vector to create the rivet, so in this case I am going to use what I called "Fake Tangents" which is basically a point which is very close to the base point. We can make a small script to keep the U Value of the Fake Tangent between 0 to 1.
threshold = 0.05
uValueFakeTan = uValue + threshold
if uValueFakeTan > 1:
uValueFakeTan = uValue - threshold
Let's create another motionPath using the uValueFakeTan on the "U value" attribute. Now we need to obtain the Fake Tangent Vector so to obtain it let's subtract the position of the fake tangent with the base point position using a plusMinusAverage.
After the node "angleBetween" the setup will remain the same as the curve rivet, this variant I call Curve Rivet with Fake Tangents.
Used curve rivet with fake tangents + parametric length
with dual joints (parent + point)
Why not obtain the "real" tangent using PoCI?
I've done many tests about it, but the only thing that we can do to keep using PoCI is using the NPOC (nearestPointOnCurve) to calculate the new position which is provided by the motionPath. That node is very heavy, so is not advisable to use it in large quantities.
Rivet on Beziers?
Sure thing, sometimes we need to do rivets using a Bezier curve, as it's known in Maya we can control the Handle and we can overlap it with the Anchor Point.
However, if we overlap both items, we cannot obtain the tangent using
PoCI. For some reason Maya doesn't calculate the tangent of that point, as we see here.
This only will happen if our parameter is 0 or 1.
If we want to pin a transform using a Bezier and we know that Handle and Anchor Point will overlap, we cannot use PoCI, the only thing that we can use is Fake Tangents + Parametric Length.
I'm sharing the scenes if you want to take a look:
That is the complete setup. I'm showing you the results using the Evaluation Toolkit of Maya. I am using an average PC, not a fancy one. In the scene:
- 201 joints.
- 5 controls, randomly animated.
- A curve or surface depending on the case.
- A transform used as worldUpObj.
- Used the matrix constraint.
- The cycle clusters were removed.
Method
|
FPS
|
Bare Motion Path
|
100
|
Matrix Rivet
|
96
|
Curve Rivet
|
90
|
Curve Rivet + Fake Tangents
|
69
|
Spline IK
|
41
|
As you can see this method is quite functional, it can be used for many things, especially for things that need a lot of range of motion. Just it require a transform to calculate the twist correctly. That's all for now and happy rigging!
UPDATED 31/03/2024: Recently I have found a error to calculate aim vector, it's already solved