multiCurveNode.cpp

//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

#include <string.h>
#include <maya/MIOStream.h>
#include <math.h>

#include <maya/MPxNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnNurbsCurve.h>
#include <maya/MFnNurbsCurveData.h>
#include <maya/MString.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MPointArray.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MArrayDataHandle.h>
#include <maya/MArrayDataBuilder.h>

class multiCurve : public MPxNode
{
public:
                        multiCurve() {};
    virtual             ~multiCurve();

    virtual MStatus     compute( const MPlug& plug, MDataBlock& data );
    static  void*       creator();
    static  MStatus     initialize();

public:
    static  MObject     inputCurve;
    static  MObject     outputCurves;
    static  MObject     numCurves;
    static  MObject     curveOffset;
    static  MTypeId     id;
};


MTypeId     multiCurve::id( 0x8000a );
MObject     multiCurve::inputCurve;
MObject     multiCurve::outputCurves;
MObject     multiCurve::numCurves;
MObject     multiCurve::curveOffset;

// Error macro: if not successful, print error message and return
// the MStatus instance containing the error code.
// Assumes that "stat" contains the error value
#define PERRORfail(stat,msg) \
                                if (!(stat)) { \
                                    stat.perror((msg)); \
                                    return (stat); \
                                }

multiCurve::~multiCurve () {}

void* multiCurve::creator()
{
    return new multiCurve();
}

MStatus multiCurve::initialize()
{
    MStatus             stat;
    MFnNumericAttribute nAttr;
    MFnTypedAttribute   typedAttr;
    
    numCurves = nAttr.create ("numCurves", "nc",
                              MFnNumericData::kLong, 5, &stat);
    PERRORfail(stat, "initialize create numCurves attribute");
    CHECK_MSTATUS ( nAttr.setKeyable( true ) );
    stat = addAttribute( numCurves );
    PERRORfail(stat, "addAttribute(numCurves)");
    
    curveOffset = nAttr.create ("curveOffset", "co",
                              MFnNumericData::kDouble, 1.0, &stat);
    PERRORfail(stat, "initialize create curveOffset attribute");
    CHECK_MSTATUS ( nAttr.setKeyable( true ) );
    stat = addAttribute( curveOffset );
    PERRORfail(stat, "addAttribute(curveOffset)");

    inputCurve = typedAttr.create( "inputCurve", "ic",
                                   MFnNurbsCurveData::kNurbsCurve, &stat );
    PERRORfail(stat, "initialize create inputCurve attribute");
    CHECK_MSTATUS ( typedAttr.setReadable( false ) );
    CHECK_MSTATUS ( typedAttr.setWritable( true ) );
    stat = addAttribute( inputCurve );
    PERRORfail(stat, "addAttribute(inputCurve)");
    
    outputCurves = typedAttr.create( "outputCurves", "oc",
                                     MFnNurbsCurveData::kNurbsCurve, &stat );
    PERRORfail(stat, "initialize create outputCurves attribute");
    CHECK_MSTATUS ( typedAttr.setArray( true ) );
    CHECK_MSTATUS ( typedAttr.setReadable( true ) );
    CHECK_MSTATUS ( typedAttr.setWritable( false ) );
    CHECK_MSTATUS ( typedAttr.setUsesArrayDataBuilder( true ) );
    stat = addAttribute( outputCurves );
    PERRORfail(stat, "addAttribute(outputCurves)");

    stat = attributeAffects( numCurves, outputCurves );
    PERRORfail(stat, "attributeAffects(inputCurve, outputCurves)");

    stat = attributeAffects( curveOffset, outputCurves );
    PERRORfail(stat, "attributeAffects(inputCurve, outputCurves)");

    stat = attributeAffects( inputCurve, outputCurves );
    PERRORfail(stat, "attributeAffects(inputCurve, outputCurves)");

    return stat;
}

MStatus multiCurve::compute( const MPlug& plug, MDataBlock& data )
{
    MStatus stat;

    if ( plug == outputCurves )
    {
        MDataHandle numCurvesHandle =  data.inputValue(numCurves, &stat);
        PERRORfail(stat, "multiCurve::compute getting numCurves");
        int num = numCurvesHandle.asLong();

        MDataHandle curveOffsetHandle =  data.inputValue(curveOffset, &stat);
        PERRORfail(stat, "multiCurve::compute getting curveOffset");
        double baseOffset = curveOffsetHandle.asDouble();

        MDataHandle inputCurveHandle = data.inputValue(inputCurve, &stat);
        PERRORfail(stat, "multiCurve::compute getting inputCurve");

        MObject inputCurveObject ( inputCurveHandle.asNurbsCurveTransformed() );
        MFnNurbsCurve inCurveFS ( inputCurveObject );

        MArrayDataHandle outputArray = data.outputArrayValue(outputCurves,
                                                             &stat);
        PERRORfail(stat, "multiCurve::compute getting output data handle");

        // Create an array data build that is preallocated to hold just
        // the number of curves we plan on creating.  When this builder
        // is set in to the MArrayDataHandle at the end of the compute
        // method, the new array will replace the existing array in the
        // scene.
        // 
        // If the number of elements of the multi does not change between
        // compute cycles, then one can reuse the space allocated on a
        // previous cycle by extracting the existing builder from the
        // MArrayDataHandle:
        //      MArrayDataBuilder builder( outputArray.builder(&stat) );
        // this later form of the builder will allow you to rewrite elements
        // of the array, and to grow it, but the array can only be shrunk by
        // explicitly removing elements with the method
        //      MArrayDataBuilder::removeElement(unsigned index);
        //
        MArrayDataBuilder builder(outputCurves, num, &stat);
        PERRORfail(stat, "multiCurve::compute creating builder");
        
        for (int curveNum = 0; curveNum < num; curveNum++) {
            MDataHandle outHandle = builder.addElement(curveNum);
            MFnNurbsCurveData dataCreator;
            MObject outCurveData = dataCreator.create();
            MObject outputCurve  = inCurveFS.copy(inputCurveObject,
                                                  outCurveData, &stat);
            PERRORfail(stat, "multiCurve::compute copying curve");

            MFnNurbsCurve outCurveFS ( outputCurve );
            MPointArray cvs;

            double offset = baseOffset * (curveNum+1);

            outCurveFS.getCVs ( cvs, MSpace::kWorld );
            int numCVs = cvs.length();
            for (int i = 0; i < numCVs; i++) {
                cvs[i].x += offset;
            }
            outCurveFS.setCVs ( cvs );

            outHandle.set(outCurveData);
        }
        
        // Set the builder back into the output array.  This statement
        // is always required, no matter what constructor was used to
        // create the builder.
        stat = outputArray.set(builder);
        PERRORfail(stat, "multiCurve::compute setting the builder");

        // Since we compute all the elements of the array, instead of
        // just marking the plug we were asked to compute as clean, mark
        // every element of the array as clean to prevent further calls
        // to this compute method during this DG evaluation cycle.
        stat = outputArray.setAllClean();
        PERRORfail(stat, "multiCurve::compute cleaning outputCurves");

    } else {
        return MS::kUnknownParameter;
    }
    
    return stat;
}

MStatus initializePlugin( MObject obj )
{
    MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");

    CHECK_MSTATUS (plugin.registerNode( "multiCurve", multiCurve::id,
                                multiCurve::creator, multiCurve::initialize ) );
    return MS::kSuccess;;
}

MStatus uninitializePlugin( MObject obj )
{
    MFnPlugin plugin( obj );

    CHECK_MSTATUS (plugin.deregisterNode( multiCurve::id ) );
    
    return MS::kSuccess;;
}