Access interactive grooming spline data using the XGen API

This topic explains how to use the xgenSplineArnoldExtension example to access XGen interactive groom spline data for third-party plug-ins, such as the Arnold for Maya renderer. You can access this example from the devkit folder of the plug-ins\xgen directory. For example, from the Maya installation package browse to:

\runTime\plug-ins\xgen\devkit.

There are three main steps to access interactive groom spline data using the XGen API.

1. Get the spline data

Use XgFnSpline class to help you operate on the splines data. It's located in the header file XgSplineApi.h. The XgFnSpline class can load the data from xgmSplineDescription.outRenderData plug in Maya. Translators use Maya's MPxData::writeBinary() method to serialize the plug data into a BLOB. XgFnSpline can interpret the data without Maya.

Load the data for XgFnSpline:

// Stream out the spline data
std::string data;
MPlug       outPlug = fnDagNode.findPlug("outRenderData");
MObject     outObj  = outPlug.asMObject();
MPxData*    outData = MFnPluginData(outObj).data();
if (outData)
{
    std::ostringstream opaqueStrm;
    outData->writeBinary(opaqueStrm);
    data = opaqueStrm.str();
}
// Load the sample for i-th motion step
_splines.load(opaqueStrm, sampleSize, sampleTime)

2. Allocate memory for the spline data

After the data is loaded into XgFnSpline object, you need to allocate memory for the data. You need to use XgItSpline to iterate with the XgFnSpline object _splines, from Step 1, and then calculate the number of points, curves and samples. You can then allocate the memory for these buffers depending on your needs.

Here is the sample code to support Arnold renderer. There are some special requirements as mentioned inline. You can also handle that depending on your specific needs.

// Count the number of curves and the number of points
// Arnold's b-spline requires two phantom points.
unsigned int curveCount        = 0;
unsigned int pointCount        = 0;
unsigned int pointInterpoCount = 0;
for (XgItSpline splineIt = _splines.iterator(); !splineIt.isDone(); splineIt.next())
{
    curveCount        += splineIt.primitiveCount();
    pointCount        += splineIt.vertexCount();
    pointInterpoCount += splineIt.vertexCount() + splineIt.primitiveCount() * 2;
}
// Get the number of motion samples
const unsigned int steps = _splines.sampleCount();
// Allocate buffers for the curves
_numPoints  = AiArrayAllocate(curveCount, 1, AI_TYPE_UINT);
_points     = AiArrayAllocate(pointInterpoCount, steps, AI_TYPE_POINT);
_radius     = AiArrayAllocate(pointCount, 1, AI_TYPE_FLOAT);
_uCoord     = AiArrayAllocate(curveCount, 1, AI_TYPE_FLOAT);
_vCoord     = AiArrayAllocate(curveCount, 1, AI_TYPE_FLOAT);
_wCoord     = AiArrayAllocate(pointInterpoCount, 1, AI_TYPE_FLOAT);

unsigned int*   numPoints   = reinterpret_cast<unsigned int*>(_numPoints->data);
SgVec3f*        points      = reinterpret_cast<SgVec3f*>(_points->data);
float*          radius      = reinterpret_cast<float*>(_radius->data);
float*          uCoord      = reinterpret_cast<float*>(_uCoord->data);
float*          vCoord      = reinterpret_cast<float*>(_vCoord->data);
float*          wCoord      = reinterpret_cast<float*>(_wCoord->data);

3. Fill the objects with values evaluated from the XgFnSpline object

In this step, you fill in these objects with the correct values you evaluated from XgFnSpline object. XGen provides an iterator called XgItSpline. It's also located in XgSplineApi.h file and provides the functionalities to help access the internal data of each spline. Some important methods are listed as follows, and should provide all the geometry information:

Varying (per-vertex) Attributes

With the information obtained from the methods above, you can fetch whatever you need and fill into your data based on your requirement. Here is the sample for obtaining this information for Arnold sample:

// Fill the array buffers for motion step 0
for (XgItSpline splineIt = _splines.iterator(); !splineIt.isDone(); splineIt.next())
{
    const unsigned int  stride         = splineIt.primitiveInfoStride();
    const unsigned int  primitiveCount = splineIt.primitiveCount();
    const unsigned int* primitiveInfos = splineIt.primitiveInfos();
    const SgVec3f*      positions      = splineIt.positions(0);
    const float*        width          = splineIt.width();
    const SgVec2f*      texcoords      = splineIt.texcoords();
    const SgVec2f*      patchUVs       = splineIt.patchUVs();

    for (unsigned int p = 0; p < primitiveCount; p++)
    {
        const unsigned int offset = primitiveInfos[p * stride];
        const unsigned int length = primitiveInfos[p * stride + 1];

        // Number of points
        *numPoints++ = length + 2;

        // Texcoord using the patch UV from the root point
        *uCoord++ = patchUVs[offset][0];
        *vCoord++ = patchUVs[offset][1];

        // Add phantom points at the beginning
        *points++ = phantomFirst(&positions[offset], length);
        *wCoord++ = phantomFirst(&texcoords[offset], length)[1];

        // Copy varying data
        for (unsigned int i = 0; i < length; i++)
        {
            *points++ = positions[offset + i];
            *radius++ = width[offset + i] * 0.5f;
            *wCoord++ = texcoords[offset + i][1];
        }

        // Add phantom points at the end
        *points++ = phantomLast(&positions[offset], length);
        *wCoord++ = phantomLast(&texcoords[offset], length)[1];

    } // for each primitive
} // for each primitive batch

// Fill the array buffers for motion step > 0
for (unsigned int key = 1; key < steps; key++)
{
    for (XgItSpline splineIt = _splines.iterator(); !splineIt.isDone(); splineIt.next())
    {
        const unsigned int  stride         = splineIt.primitiveInfoStride();
        const unsigned int  primitiveCount = splineIt.primitiveCount();
        const unsigned int* primitiveInfos = splineIt.primitiveInfos();
        const SgVec3f*      positions      = splineIt.positions(key);

        for (unsigned int p = 0; p < primitiveCount; p++)
        {
            const unsigned int offset = primitiveInfos[p * stride];
            const unsigned int length = primitiveInfos[p * stride + 1];

            // Add phantom points at the beginning
            *points++ = phantomFirst(&positions[offset], length);

            // Copy varying data
            for (unsigned int i = 0; i < length; i++)
            {
                *points++ = positions[offset + i];
            }

            // Add phantom points at the end
            *points++ = phantomLast(&positions[offset], length);

        } // for each primitive
    } // for each primitive batch
} // for each motion step
// Set the buffers to the curves node
AiNodeSetArray(_curves, "num_points", _numPoints);
AiNodeSetArray(_curves, "points", _points);
AiNodeSetArray(_curves, "radius", _radius);
AiNodeSetArray(_curves, "uparamcoord", _uCoord);
AiNodeSetArray(_curves, "vparamcoord", _vCoord);
AiNodeSetArray(_curves, "wparamcoord", _wCoord);