Appendix A: NURBS Geometry
 
 
 

There are quite a few really good books on spline geometry and NURBS geometry. This appendix will not try to teach you everything about NURBS but will try to give you a general overview of the Maya particulars.

Of the six curves in the illustration, the first is a circle primitive with four spans. Notice how the circle does not touch the hull. The next five curves are attempts at replicating this geometry using the curve building tools in Maya.

The circle appears to have four CVs using the default options of the curve tool ( Create > CV Curve Tool), with the following options set:

Multiple End Knots

ON

Curve Degree

3 Cubic

If you place four CVs, you produce something that looks like “Curve 1”. This is an Open curve. It’s open because the curve has a gap between the first and last CVs. It also has only one span, where the circle has four. (A span connects two edit points.)

If you try to close the curve by placing another CV on top of the first CV, you produce something that looks like Curve 2. Curve 2 is closer to the primitive circle, first because it is Closed, that is, there is no gap in the curve between the first and last CVs, and second, because it has two spans. However, you will notice that the curve does not look like a circle, it intersects the hull at the first CV and there is a discontinuity in the curve’s tangent at this point. If you were to place another CV overlapping the second CV, you would find that the curve is now open and looks even less like a circle. A third and fourth CV don’t help either, all because the first and last CVs are always on the hull.

If you go into the curve tool’s option box and turn off Multiple End Knots and place four CVs, you produce a curve which looks like Curve 3. This looks more promising than Curve 1 since the curve does not intersect the hull. If you now place a fifth CV on top of the first CV you produce Curve 4. This still looks promising. If you go further and add a sixth and seventh CV on top of the second and third CV, you produce Curve 5. This looks exactly like the circle. You’ve done it.

Well, not quite. If you were now to pull one of these additional CVs away from the CV under it, you would pull the curve apart, producing an open curve again. However, no matter how you pull the CVs on the circle you cannot pull it apart, it remains a closed curve. So there is still a small difference between these two curves. The difference is the form of the curve. Curves 1, 3 and 4 are all Open, that is, their form is open. Curves 2 and 5 are closed, that is, their form is closed. The circle is neither open nor closed, it’s form is a third type, called periodic. Periodic implies that the last degree CVs of the curve overlap the first degree CVs, and all operations on the CVs ensure that they stay together and cannot be pulled apart.

A periodic curve generally has tangency continuity on the whole curve while a closed curve will not.

Examples

The following plug-in creates Curve 1. To ensure that the CVs interpolate the end points of the curve, the knots of the curve are duplicated (piled up) at each end.

#include <maya/MSimple.h>
#include <maya/MIOStream.h>
#include <maya/MPointArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MFnNurbsCurve.h>
MStatus curveTest( const MArgList& )
{
 MFnNurbsCurve curveFn;
 const double cvs[][4] = {
 { -1, 0, -1, 1 },
 { -1, 0, 1, 1 },
 { 1, 0, 1, 1 },
 { 1, 0, -1, 1 }
 };
 const double knots[] = { 0, 0, 0, 1, 1, 1 };
 MPointArray cvArray( cvs, unsigned( sizeof(cvs) / (4*sizeof(double)) ) );
 MDoubleArray knotArray( knots, unsigned(sizeof( knots )/sizeof( double )) );
 MStatus status;
 MObject curve = curveFn.create( cvArray, knotArray, 3, MFnNurbsCurve::kOpen,
 false, false, MObject::kNullObj, &status );
 if ( MS::kSuccess != status )
 {
 cout << “Failed to create curve\n”;
 return status;
 }
 return MS::kSuccess;
}
DeclareSingle( curveTest );

The only change necessary to produce Curve 3 from Curve 1 is simply to change the knot vector so that the knots are not piled up at the ends of the curve:

const double knots[] = { 0, 1, 2, 3, 4, 5 };

Changing Curve 1 to Curve 2 is a little more involved. A CV, a duplicate of the first, has to be added to the end of the CV array, and a new knot must be inserted into the knot array.

const double cvs[][4] = {
 { -1, 0, -1, 1 },
 { -1, 0, 1, 1 },
 { 1, 0, 1, 1 },
 { 1, 0, -1, 1 },
 { -1, 0, -1, 1 }
};
const double knots[] = { 0, 0, 0, 1, 2, 2, 2 };

Curve 4 is to Curve 3 what Curve 2 is to Curve 1, that is, just an additional CV (a duplicate of the first) and an additional knot.

const double cvs[][4] = {
 { -1, 0, -1, 1 },
 { -1, 0, 1, 1 },
 { 1, 0, 1, 1 },
 { 1, 0, -1, 1 },
 { -1, 0, -1, 1 }
};
const double knots[] = { 0, 1, 2, 3, 4, 5, 6 };

Curve 5 continues on from Curve 4 with an additional two CVs (duplicating the second and third) and two knots.

const double cvs[][4] = {
 { -1, 0, -1, 1 },
 { -1, 0, 1, 1 },
 { 1, 0, 1, 1 },
 { 1, 0, -1, 1 },
 { -1, 0, -1, 1 },
 { -1, 0, 1, 1 },
 { 1, 0, 1, 1 }
};
const double knots[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };

This will produce a curve identical in shape to the circle primitive, however if you were to start pulling on the CVs of this curve you would find that you could pull the curve apart. To keep the curve from separating one additional change is required. When creating the curve, rather than specifying it be open (MFnNurbsCurve::kOpen) specify that it should be periodic (MFnNurbsCurve::kPeriodic).

MObject curve = curveFn.create( cvArray, knotArray, 3,
 MFnNurbsCurve::kPeriodic, false, false, MObject::kNullObj, &status );

So long as there is the proper number of overlapping CVs (one for each degree of the curve - these curves are degree three, so there should be three overlapping CVs) you can create a periodic curve. If there are insufficient overlapping CVs, the create() method will fail.