Today, I am going to share a really quick tip of achieving an uniform spacing along a curve.
Disclaimer: If you are not familiar with using the API, worry not, we are looking at a very simple example and I will try to explain everything, but it also might be a good idea to get some understanding of how it all functions. A good place to start is Chad Vernon’s Introduction to the API.
Very often in rigging we need to use curves. In quite a lot of these cases we need to get uniformly distributed positions along that curve. A simple example is creating controls along a curve. Chances are you would want them to be as uniformly distributed as possible, but in order to get that only using the parameter along the curve, you would need a perfectly uniform one that also matches the actual curvature. To get that you would need to do a lot of rebuilding, inserting knots and tweaking.
For another tip on rigging with curves have a look at my post about getting a stable end joint when working with IK splines.
I suppose that if you are doing it by hand then you can easily tweak the position along the curve and eyeball the distances between them to be roughly equal, but it sounds like too much hassle to me and also, more often than not, you would want to have that automated as I could imagine it being integral to a lot of rig components.
Let us have a look then!
So, I am sure everyone has run into the situation where they’ve wanted to create a few objects positioned uniformly along a
nurbsCurve or a
nurbsSurface, but they get this.
Notice how larger the gap is between the joints on the left-hand side than on the right. The reason for that is that the distance between the isoparms is not equal throughout the surface, but the parameter difference is. What that means is, no matter how much we stretch and deform the surface, the parameter difference between the spans is always going to be the same – .25 in our example (1.0 /
That discrepancy between the parameter space and the 3D space is what causes these non-uniform positions.
Getting uniform positions along a curve
So now that we know that, we can figure out that the way to get a reliable position is to find a relationship between the 3D space and the parameter space. That is where the API’s
MFnNurbsCurve comes handy.
The 3D space information that we are going to be using is the length of the curve, as we know that is an accurate representation of distance along the curve. If you have a look at the available methods in the
MFnNurbsCurve class, you will find the following one
findParamFromLength. Given a distance along the curve this function will give us a parameter.
Let us consider the following curve.
Let us position some joints along the curve using distances only based on the
for i in range(11): pci = mc.createNode("pointOnCurveInfo") mc.connectAttr("curve1.worldSpace", pci + ".inputCurve") mc.setAttr(pci+".parameter", i * .1) jnt = mc.createNode("joint") mc.connectAttr(pci+".position",jnt+".t")
All we do here is iterate 11 times and create a joint on the curve at the position of parameter equal to
iterator * step where the
1.0 / (numberOfJoints - 1), which is
.1 in our example.
As expected, the non-uniform distance between the CVs results in an also non-uniform spacing of the joints.
Let us try a different approach then. We will get a reference to an API instance of our curve, and using the above mentioned function we will get parameters based on actual distance along the curve, hence getting an uniform distribution.
from maya import OpenMaya as om def getDagPath(node=None): sel = om.MSelectionList() sel.add(node) d = om.MDagPath() sel.getDagPath(0, d) return d crvFn = om.MFnNurbsCurve(getDagPath("curveShape4")) for i in range(11): parameter = crvFn.findParamFromLength(crvFn.length() * .1 * i) point = om.MPoint() crvFn.getPointAtParam(parameter, point) jnt = mc.createNode("joint") mc.xform(jnt,t=[point.x,point.y,point.z])
getDagPath function takes a name of a node and returns an
MDagPath instance of that node, which we need in order to create the
MFnNurbsCurve instance. The
MDagPath is used for many other things in the API, so it is always a good idea to have that
getDagPath function somewhere where you can easily access it.
Notice we are passing the curve shape node, as if we are to use the
curve4 transform we will not be able to create the
MFnNurbsCurve, we iterate 11 times and following the same logic for getting a position along the curve as before –
iterator * step – we get the
parameter at that position, using the
Now that we know the
parameter we could still use the
pointOnCurveInfo as we did before, but considering we are already working in the API we might as well get all the data from there. So, using the
getPointAtParam method we can get a world space position of the point on the curve at that parameter.
Notice however that we are first creating an
MPoint and we are then passing it to the
getPointAtParam function to populate it.
And here is the result.
Using the same approach to get uniform positions on a surface
So, all that
nurbsCurve business is great, but how can we apply the same logic to a
nurbsSurface. Unfortunately, the
MFnNurbsSurface does not have any method resembling the
findParamFromLength one, but luckily we can always create a curve from a surface.
So in order to get uniform spacing along a
nurbsSurface what I usually would do is create a
nurbsCurve from that surface using the
curveFromSurfaceIso node and using the described method find the accurate parameters and use those on the surface itself.
While writing this I realized that maybe the same approach can be used to actually get an uniform representation of the surface by getting curves from the surface and using them calculating the new, uniformly spaced CVs of the surface. Seems like we might loose a lot of the curvature of the surface, but it also seems promising, so I will definitely look into it.
Using curves and surfaces is something that I did not do a lot of in the beginning of my rigging path, but obviously they are such an integral part of rigging, that it is very important to be able to work with them in a reliable and predictable fashion. Thus, this tip has helped me a lot when building bits of my rigging system and I really hope you find it valuable in your work as well.
Additionally, I would like to reiterate who powerful of a tool the API is and I would definitely suggest anyone who is not really familiar with it to take the plunge and start learning it by using it. The major benefits are not only functional ones (like the one described in this post), but also performance ones, as the API is incredibly faster that anything to do with