apiMeshShape.cpp

//-
// ==========================================================================
// Copyright 2010 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.
// ==========================================================================
//+

//
// apiMeshShape.cpp
//

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

#include <apiMeshShape.h>
#include <apiMeshShapeUI.h>
#include <apiMeshGeometryOverride.h>
#include <apiMeshCreator.h>
#include <apiMeshData.h>
#include <api_macros.h>

#include <maya/MFnPlugin.h>
#include <maya/MFnPluginData.h>
#include <maya/MDrawRegistry.h>
#include <maya/MMatrix.h>
#include <maya/MAttributeSpecArray.h>
#include <maya/MAttributeSpec.h>
#include <maya/MAttributeIndex.h>
#include <maya/MObjectArray.h>
#include <maya/MFnSingleIndexedComponent.h>
#include <maya/MDagPath.h>
#include <maya/MFnAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MPointArray.h>
#include <maya/MViewport2Renderer.h>

bool debug = false;

//
// Shape implementation
//

MObject apiMesh::inputSurface;
MObject apiMesh::outputSurface;
MObject apiMesh::cachedSurface;
MObject apiMesh::worldSurface;
MObject apiMesh::bboxCorner1;
MObject apiMesh::bboxCorner2;

MString apiMesh::drawDbClassification("drawdb/geometry/apiMesh");
MString apiMesh::drawRegistrantId("apiMeshPlugin");

MTypeId apiMesh::id( 0x80099 );

apiMesh::apiMesh() {}
apiMesh::~apiMesh() {}

//
// Overrides
//

/* override */
void apiMesh::postConstructor()
//
// Description
//
//    When instances of this node are created internally, the MObject associated
//    with the instance is not created until after the constructor of this class
//    is called. This means that no member functions of MPxSurfaceShape can
//    be called in the constructor.
//    The postConstructor solves this problem. Maya will call this function
//    after the internal object has been created.
//    As a general rule do all of your initialization in the postConstructor.
//
{
    // This call allows the shape to have shading groups assigned
    //
    setRenderable( true );

    // Is there input history to this node
    //
    fHasHistoryOnCreate = false;
}

/* override */
MStatus apiMesh::compute( const MPlug& plug, MDataBlock& datablock )
//
// Description
//
//    When input attributes are dirty this method will be called to
//    recompute the output attributes.
//
// Arguments
//
//    plug      - the attribute that triggered the compute
//    datablock - the nodes data
//
// Returns
//
//    kSuccess          - this method could compute the dirty attribute,
//    kUnknownParameter - the dirty attribute can not be handled at this level
//
{
    if (debug)
        cerr << "apiMesh::compute : plug " << plug.info() << endl;

    if ( plug == outputSurface ) {
        return computeOutputSurface( plug, datablock );
    }
    else if ( plug == cachedSurface ) {
        return computeOutputSurface( plug, datablock );
    }
    else if ( plug == worldSurface ) {
        return computeWorldSurface( plug, datablock );
    }
    else {
        return MS::kUnknownParameter;
    }
}

/* override */
//
// Description
//
//  Horribly abuse the purpose of this method to notify the Viewport 2.0
//  renderer that something about this shape has changed and that it should
//  be retranslated.
//
MStatus apiMesh::setDependentsDirty( const MPlug& plug, MPlugArray& plugArray)
{
    // if the dirty attribute is the output mesh then we need to signal the
    // the renderer that it needs to update the object

    if ( plug == inputSurface ) {
        MHWRender::MRenderer::setGeometryDrawDirty(thisMObject());
    }
    return MS::kSuccess;
}

/* override */
//
// Description
//
//    Handle internal attributes.
//
//    Attributes that require special storage, bounds checking,
//    or other non-standard behavior can be marked as "Internal" by
//    using the "MFnAttribute::setInternal" method.
//
//    The get/setInternalValue methods will get called for internal
//    attributes whenever the attribute values are stored or retrieved
//    using getAttr/setAttr or MPlug getValue/setValue.
//
//    The inherited attribute mControlPoints is internal and we want
//    its values to get stored only if there is input history. Otherwise
//    any changes to the vertices are stored in the cachedMesh and outputMesh
//    directly.
//
//    If values are retrieved then we want the controlPoints value
//    returned if there is history, this will be the offset or tweak.
//    In the case of no history, the vertex position of the cached mesh
//    is returned.
//
bool apiMesh::getInternalValue( const MPlug& plug, MDataHandle& result )
{
    bool isOk = true;

    if( (plug == mControlPoints) ||
        (plug == mControlValueX) ||
        (plug == mControlValueY) ||
        (plug == mControlValueZ) )
    {
        // If there is input history then the control point value is
        // directly returned. This is the tweak or offset that
        // was applied to the vertex.
        //
        // If there is no input history then return the actual vertex
        // position and ignore the controlPoints attribute.
        //
        if ( hasHistory() ) {
            return MPxNode::getInternalValue( plug, result );
        }
        else {
            double val = 0.0;
            if ( (plug == mControlPoints) && !plug.isArray() ) {
                MPoint pnt;
                int index = plug.logicalIndex();
                value( index, pnt );
                result.set( pnt[0], pnt[1], pnt[2] );
            }
            else if ( plug == mControlValueX ) {
                MPlug parentPlug = plug.parent();
                int index = parentPlug.logicalIndex();
                value( index, 0, val );
                result.set( val );
            }
            else if ( plug == mControlValueY ) {
                MPlug parentPlug = plug.parent();
                int index = parentPlug.logicalIndex();
                value( index, 1, val );
                result.set( val );
            }
            else if ( plug == mControlValueZ ) {
                MPlug parentPlug = plug.parent();
                int index = parentPlug.logicalIndex();
                value( index, 2, val );
                result.set( val );
            }
        }
    }
    // This inherited attribute is used to specify whether or
    // not this shape has history. During a file read, the shape
    // is created before any input history can get connected.
    // This attribute, also called "tweaks", provides a way to
    // for the shape to determine if there is input history
    // during file reads.
    //
    else if ( plug == mHasHistoryOnCreate ) {
        result.set( fHasHistoryOnCreate );
    }
    else {
        isOk = MPxSurfaceShape::getInternalValue( plug, result );
    }

    return isOk;
}

/* override */
//
// Description
//
//    Handle internal attributes.
//
//    Attributes that require special storage, bounds checking,
//    or other non-standard behavior can be marked as "Internal" by
//    using the "MFnAttribute::setInternal" method.
//
//    The get/setInternalValue methods will get called for internal
//    attributes whenever the attribute values are stored or retrieved
//    using getAttr/setAttr or MPlug getValue/setValue.
//
//    The inherited attribute mControlPoints is internal and we want
//    its values to get stored only if there is input history. Otherwise
//    any changes to the vertices are stored in the cachedMesh and outputMesh
//    directly.
//
//    If values are retrieved then we want the controlPoints value
//    returned if there is history, this will be the offset or tweak.
//    In the case of no history, the vertex position of the cached mesh
//    is returned.
//
bool apiMesh::setInternalValue( const MPlug& plug, const MDataHandle& handle )
{
    bool isOk = true;

    if( (plug == mControlPoints) ||
        (plug == mControlValueX) ||
        (plug == mControlValueY) ||
        (plug == mControlValueZ) )
    {
        // If there is input history then set the control points value
        // using the normal mechanism. In this case we are setting
        // the tweak or offset that will get applied to the input
        // history.
        //
        // If there is no input history then ignore the controlPoints
        // attribute and set the vertex position directly in the
        // cachedMesh.
        //
        if ( hasHistory() ) {
            verticesUpdated();
            return MPxNode::setInternalValue( plug, handle );
        }
        else {
            if( plug == mControlPoints && !plug.isArray()) {
                int index = plug.logicalIndex();
                MPoint point;
                double3& ptData = handle.asDouble3();
                point.x = ptData[0];
                point.y = ptData[1];
                point.z = ptData[2];
                setValue( index, point );
            }
            else if( plug == mControlValueX ) {
                MPlug parentPlug = plug.parent();
                int index = parentPlug.logicalIndex();
                setValue( index, 0, handle.asDouble() );
            }
            else if( plug == mControlValueY ) {
                MPlug parentPlug = plug.parent();
                int index = parentPlug.logicalIndex();
                setValue( index, 1, handle.asDouble() );
            }
            else if( plug == mControlValueZ ) {
                MPlug parentPlug = plug.parent();
                int index = parentPlug.logicalIndex();
                setValue( index, 2, handle.asDouble() );
            }
        }
    }
    // This inherited attribute is used to specify whether or
    // not this shape has history. During a file read, the shape
    // is created before any input history can get connected.
    // This attribute, also called "tweaks", provides a way to
    // for the shape to determine if there is input history
    // during file reads.
    //
    else if ( plug == mHasHistoryOnCreate ) {
        fHasHistoryOnCreate = handle.asBool();
    }
    else {
        isOk = MPxSurfaceShape::setInternalValue( plug, handle );
    }

    return isOk;
}

/* override */
MStatus apiMesh::connectionMade( const MPlug& plug,
                                 const MPlug& otherPlug,
                                 bool asSrc )
//
// Description
//
//    Whenever a connection is made to this node, this method
//    will get called.
//
{
    if ( plug == inputSurface ) {
        MStatus stat;
        MObject thisObj = thisMObject();
        MPlug historyPlug( thisObj, mHasHistoryOnCreate );
        stat = historyPlug.setValue( true );
        MCHECKERROR( stat, "connectionMade: setValue(mHasHistoryOnCreate)" );
    }

    return MPxNode::connectionMade( plug, otherPlug, asSrc );
}

/* override */
MStatus apiMesh::connectionBroken( const MPlug& plug,
                                   const MPlug& otherPlug,
                                   bool asSrc )
//
// Description
//
//    Whenever a connection to this node is broken, this method
//    will get called.
//
{
    if ( plug == inputSurface ) {
        MStatus stat;
        MObject thisObj = thisMObject();
        MPlug historyPlug( thisObj, mHasHistoryOnCreate );
        stat = historyPlug.setValue( false );
        MCHECKERROR( stat, "connectionBroken: setValue(mHasHistoryOnCreate)" );
    }

    return MPxNode::connectionBroken( plug, otherPlug, asSrc );
}

/* override */
MStatus apiMesh::shouldSave( const MPlug& plug, bool& result )
//
// Description
//
//    During file save this method is called to determine which
//    attributes of this node should get written. The default behavior
//    is to only save attributes whose values differ from the default.
//
//
//
{
    MStatus status = MS::kSuccess;

    if( plug == mControlPoints || plug == mControlValueX ||
        plug == mControlValueY || plug == mControlValueZ )
    {
        if( hasHistory() ) {
            // Calling this will only write tweaks if they are
            // different than the default value.
            //
            status = MPxNode::shouldSave( plug, result );
        }
        else {
            result = false;
        }
    }
    else if ( plug == cachedSurface ) {
        if ( hasHistory() ) {
            result = false;
        }
        else {
            MObject data;
            status = plug.getValue( data );
            MCHECKERROR( status, "shouldSave: MPlug::getValue" );
            result = ( ! data.isNull() );
        }
    }
    else {
        status = MPxNode::shouldSave( plug, result );
    }

    return status;
}

/* override */
void apiMesh::componentToPlugs( MObject & component,
                                MSelectionList & list ) const
//
// Description
//
//    Converts the given component values into a selection list of plugs.
//    This method is used to map components to attributes.
//
// Arguments
//
//    component - the component to be translated to a plug/attribute
//    list      - a list of plugs representing the passed in component
//
{
    if ( component.hasFn(MFn::kSingleIndexedComponent) ) {

        MFnSingleIndexedComponent fnVtxComp( component );
        MObject thisNode = thisMObject();
        MPlug plug( thisNode, mControlPoints );
        // If this node is connected to a tweak node, reset the
        // plug to point at the tweak node.
        //
        convertToTweakNodePlug(plug);

        int len = fnVtxComp.elementCount();

        for ( int i = 0; i < len; i++ )
        {
            plug.selectAncestorLogicalIndex(fnVtxComp.element(i),
                                            plug.attribute());
            list.add(plug);
        }
    }
}

/* override */
MPxSurfaceShape::MatchResult
apiMesh::matchComponent( const MSelectionList& item,
                      const MAttributeSpecArray& spec,
                      MSelectionList& list )
//
// Description:
//
//    Component/attribute matching method.
//    This method validates component names and indices which are
//    specified as a string and adds the corresponding component
//    to the passed in selection list.
//
//    For instance, select commands such as "select shape1.vtx[0:7]"
//    are validated with this method and the corresponding component
//    is added to the selection list.
//
// Arguments
//
//    item - DAG selection item for the object being matched
//    spec - attribute specification object
//    list - list to add components to
//
// Returns
//
//    the result of the match
//
{
    MPxSurfaceShape::MatchResult result = MPxSurfaceShape::kMatchOk;
    MAttributeSpec attrSpec = spec[0];
    int dim = attrSpec.dimensions();

    // Look for attributes specifications of the form :
    //     vtx[ index ]
    //     vtx[ lower:upper ]
    //
    if ( (1 == spec.length()) && (dim > 0) && (attrSpec.name() == "vtx") ) {
        int numVertices = meshGeom()->vertices.length();
        MAttributeIndex attrIndex = attrSpec[0];

        int upper = 0;
        int lower = 0;
        if ( attrIndex.hasLowerBound() ) {
            attrIndex.getLower( lower );
        }
        if ( attrIndex.hasUpperBound() ) {
            attrIndex.getUpper( upper );
        }

        // Check the attribute index range is valid
        //
        if ( (lower > upper) || (upper >= numVertices) ) {
            result = MPxSurfaceShape::kMatchInvalidAttributeRange;
        }
        else {
            MDagPath path;
            item.getDagPath( 0, path );
            MFnSingleIndexedComponent fnVtxComp;
            MObject vtxComp = fnVtxComp.create( MFn::kMeshVertComponent );

            for ( int i=lower; i<=upper; i++ )
            {
                fnVtxComp.addElement( i );
            }
            list.add( path, vtxComp );
        }
    }
    else {
        // Pass this to the parent class
        return MPxSurfaceShape::matchComponent( item, spec, list );
    }

    return result;
}

/* override */
bool apiMesh::match( const MSelectionMask & mask,
                     const MObjectArray& componentList ) const
//
// Description:
//
//      Check for matches between selection type / component list, and
//      the type of this shape / or it's components
//
//      This is used by sets and deformers to make sure that the selected
//      components fall into the "vertex only" category.
//
// Arguments
//
//      mask          - selection type mask
//      componentList - possible component list
//
// Returns
//      true if matched any
//
{
    bool result = false;

    if( componentList.length() == 0 ) {
        result = mask.intersects( MSelectionMask::kSelectMeshes );
    }
    else {
        for ( int i=0; i<(int)componentList.length(); i++ ) {
            if ( (componentList[i].apiType() == MFn::kMeshVertComponent) &&
                 (mask.intersects(MSelectionMask::kSelectMeshVerts))
            ) {
                result = true;
                break;
            }
        }
    }
    return result;
}

/* override */
MObject apiMesh::createFullVertexGroup() const
//
// Description
//     This method is used by maya when it needs to create a component
//     containing every vertex (or control point) in the shape.
//     This will get called if you apply some deformer to the whole
//     shape, i.e. select the shape in object mode and add a deformer to it.
//
// Returns
//
//    A "complete" component representing all vertices in the shape.
//
{
    // Create a vertex component
    //
    MFnSingleIndexedComponent fnComponent;
    MObject fullComponent = fnComponent.create( MFn::kMeshVertComponent );

    // Set the component to be complete, i.e. the elements in
    // the component will be [0:numVertices-1]
    //
    int numVertices = ((apiMesh*)this)->meshGeom()->vertices.length();
    fnComponent.setCompleteData( numVertices );

    return fullComponent;
}

/* override */
MObject apiMesh::localShapeInAttr() const
//
// Description
//
//    Returns the input attribute of the shape. This is used by
//    maya to establish input connections for deformers etc.
//    This attribute must be data of tye kGeometryData.
//
// Returns
//
//    input attribute for the shape
//
{
    return inputSurface;
}

/* override */
MObject apiMesh::localShapeOutAttr() const
//
// Description
//
//    Returns the output attribute of the shape. This is used by
//    maya to establish out connections for deformers etc.
//    This attribute must be data of tye kGeometryData.
//
// Returns
//
//    output attribute for the shape
//
//
{
    return outputSurface;
}

/* override */
MObject apiMesh::worldShapeOutAttr() const
//
// Description
//
//    Returns the world space output "array" attribute of the shape.
//    This is used by maya to establish out connections for deformers etc.
//    This attribute must be an array attribute, each element representing
//    a particular instance of the shape.
//    This attribute must be data of type kGeometryData.
//
// Returns
//
//    world space "array" attribute for the shape
//
{
    return worldSurface;
}

/* override */
MObject apiMesh::cachedShapeAttr() const
//
// Description
//
//    Returns the cached shape attribute of the shape.
//    This attribute must be data of type kGeometryData.
//
// Returns
//
//    cached shape attribute
//
{
    return cachedSurface;
}



/* override */
MObject apiMesh::geometryData() const
//
// Description
//
//    Returns the data object for the surface. This gets
//    called internally for grouping (set) information.
//
{
    apiMesh* nonConstThis = (apiMesh*)this;
    MDataBlock datablock = nonConstThis->forceCache();
    MDataHandle handle = datablock.inputValue( inputSurface );
    return handle.data();
}

/*override */
void apiMesh:: closestPoint ( const MPoint & toThisPoint, \
                MPoint & theClosestPoint, double tolerance ) const
//
// Description
//
//      Returns the closest point to the given point in space.
//      Used for rigid bind of skin.  Currently returns wrong results;
//      override it by implementing a closest point calculation.
{
    // Iterate through the geometry to find the closest point within
    // the given tolerance.
    //
    apiMeshGeom* geomPtr = ((apiMesh*)this)->meshGeom();
    int numVertices = geomPtr->vertices.length();
    for (int ii=0; ii<numVertices; ii++)
    {
        MPoint tryThisOne = geomPtr->vertices[ii];
    }

    // Set the output point to the result (hardcode for debug just now)
    //
    theClosestPoint = geomPtr->vertices[0];
}

/* override */
void apiMesh::transformUsing( const MMatrix & mat,
                              const MObjectArray & componentList )
//
// Description
//
//    Transforms by the matrix the given components, or the entire shape
//    if the componentList is empty. This method is used by the freezeTransforms command.
//
// Arguments
//
//    mat           - matrix to tranform the components by
//    componentList - list of components to be transformed,
//                    or an empty list to indicate the whole surface
//
{
    // Let the other version of transformUsing do the work for us.
    //
    transformUsing( mat,
                    componentList,
                    MPxSurfaceShape::kNoPointCaching,
                    NULL);
}


//
// Description
//
//    Transforms the given components. This method is used by
//    the move, rotate, and scale tools in component mode.
//    The bounding box has to be updated here, so do the normals and
//    any other attributes that depend on vertex positions.
//
// Arguments
//    mat           - matrix to tranform the components by
//    componentList - list of components to be transformed,
//                    or an empty list to indicate the whole surface
//    cachingMode   - how to use the supplied pointCache
//    pointCache    - if non-null, save or restore points from this list base
//                    on the cachingMode
//
void apiMesh::transformUsing( const MMatrix & mat,
                              const MObjectArray & componentList,
                              MVertexCachingMode cachingMode,
                              MPointArray* pointCache)
{

    MStatus stat;
    apiMeshGeom* geomPtr = meshGeom();

    bool savePoints    = (cachingMode == MPxSurfaceShape::kSavePoints);
    unsigned int i=0,j=0;
    unsigned int len = componentList.length();

    if (cachingMode == MPxSurfaceShape::kRestorePoints) {
        // restore the points based on the data provided in the pointCache attribute
        //
        unsigned int cacheLen = pointCache->length();
        if (len > 0) {
            // traverse the component list
            //
            for ( i = 0; i < len && j < cacheLen; i++ )
            {
                MObject comp = componentList[i];
                MFnSingleIndexedComponent fnComp( comp );
                int elemCount = fnComp.elementCount();
                for ( int idx=0; idx<elemCount && j < cacheLen; idx++, ++j ) {
                    int elemIndex = fnComp.element( idx );
                    geomPtr->vertices[elemIndex] = (*pointCache)[j];
                }
            }
        } else {
            // if the component list is of zero-length, it indicates that we
            // should transform the entire surface
            //
            len = geomPtr->vertices.length();
            for ( unsigned int idx = 0; idx < len && j < cacheLen; ++idx, ++j ) {
                geomPtr->vertices[idx] = (*pointCache)[j];
            }
        }
    } else {
        // Transform the surface vertices with the matrix.
        // If savePoints is true, save the points to the pointCache.
        //
        if (len > 0) {
            // Traverse the componentList
            //
            for ( i=0; i<len; i++ )
            {
                MObject comp = componentList[i];
                MFnSingleIndexedComponent fnComp( comp );
                int elemCount = fnComp.elementCount();

                if (savePoints && 0 == i) {
                    pointCache->setSizeIncrement(elemCount);
                }
                for ( int idx=0; idx<elemCount; idx++ )
                {
                    int elemIndex = fnComp.element( idx );
                    if (savePoints) {
                        pointCache->append(geomPtr->vertices[elemIndex]);
                    }
                    geomPtr->vertices[elemIndex] *= mat;
                    geomPtr->normals[idx] =
                        geomPtr->normals[idx].transformAsNormal( mat );
                }
            }
        } else {
            // If the component list is of zero-length, it indicates that we
            // should transform the entire surface
            //
            len = geomPtr->vertices.length();
            if (savePoints) {
                pointCache->setSizeIncrement(len);
            }
            for ( unsigned int idx = 0; idx < len; ++idx ) {
                if (savePoints) {
                    pointCache->append(geomPtr->vertices[idx]);
                }
                geomPtr->vertices[idx] *= mat;
                geomPtr->normals[idx] =
                    geomPtr->normals[idx].transformAsNormal( mat );

            }
        }
    }
    // Retrieve the value of the cached surface attribute.
    // We will set the new geometry data into the cached surface attribute
    //
    // Access the datablock directly. This code has to be efficient
    // and so we bypass the compute mechanism completely.
    // NOTE: In general we should always go though compute for getting
    // and setting attributes.
    //
    MDataBlock datablock = forceCache();

    MDataHandle cachedHandle = datablock.outputValue( cachedSurface, &stat );
    MCHECKERRORNORET( stat, "computeInputSurface error getting cachedSurface")
    apiMeshData* cached = (apiMeshData*) cachedHandle.asPluginData();

    MDataHandle dHandle = datablock.outputValue( mControlPoints, &stat );
    MCHECKERRORNORET( stat, "transformUsing get dHandle" )

    // If there is history then calculate the tweaks necessary for
    // setting the final positions of the vertices.
    //
    if ( hasHistory() && (NULL != cached) ) {
        // Since the shape has history, we need to store the tweaks (deltas)
        // between the input shape and the tweaked shape in the control points
        // attribute.
        //
        stat = buildControlPoints( datablock, geomPtr->vertices.length() );
        MCHECKERRORNORET( stat, "transformUsing buildControlPoints" )

        MArrayDataHandle cpHandle( dHandle, &stat );
        MCHECKERRORNORET( stat, "transformUsing get cpHandle" )

        // Loop through the component list and transform each vertex.
        //
        for ( i=0; i<len; i++ )
        {
            MObject comp = componentList[i];
            MFnSingleIndexedComponent fnComp( comp );
            int elemCount = fnComp.elementCount();
            for ( int idx=0; idx<elemCount; idx++ )
            {
                int elemIndex = fnComp.element( idx );
                cpHandle.jumpToElement( elemIndex );
                MDataHandle pntHandle = cpHandle.outputValue();
                double3& pnt = pntHandle.asDouble3();

                MPoint oldPnt = cached->fGeometry->vertices[elemIndex];
                MPoint newPnt = geomPtr->vertices[elemIndex];
                MPoint offset = newPnt - oldPnt;

                pnt[0] += offset[0];
                pnt[1] += offset[1];
                pnt[2] += offset[2];
            }
        }
    }

    // Copy outputSurface to cachedSurface
    //
    if ( NULL == cached ) {
        cerr << "NULL cachedSurface data found\n";
    }
    else {
        *(cached->fGeometry) = *geomPtr;
    }

    MPlug pCPs(thisMObject(),mControlPoints);
    pCPs.setValue(dHandle);

    // Moving vertices will likely change the bounding box.
    //
    computeBoundingBox( datablock );

    // Tell maya the bounding box for this object has changed
    // and thus "boundingBox()" needs to be called.
    //
    childChanged( MPxSurfaceShape::kBoundingBoxChanged );
}

//
// Description
//
//    Transforms the given components. This method is used by
//    the move, rotate, and scale tools in component mode when the
//    tweaks for the shape are stored on a separate tweak node.
//    The bounding box has to be updated here, so do the normals and
//    any other attributes that depend on vertex positions.
//
// Arguments
//    mat           - matrix to tranform the components by
//    componentList - list of components to be transformed,
//                    or an empty list to indicate the whole surface
//    cachingMode   - how to use the supplied pointCache
//    pointCache    - if non-null, save or restore points from this list base
//                    on the cachingMode
//    handle        - handle to the attribute on the tweak node where the
//                    tweaks should be stored
//
/* override */
void
apiMesh::tweakUsing( const MMatrix & mat,
                     const MObjectArray & componentList,
                     MVertexCachingMode cachingMode,
                     MPointArray* pointCache,
                     MArrayDataHandle& handle )
{
    apiMeshGeom* geomPtr = meshGeom();

    bool savePoints    = (cachingMode == MPxSurfaceShape::kSavePoints);
    bool updatePoints  = (cachingMode == MPxSurfaceShape::kUpdatePoints);

    MArrayDataBuilder builder = handle.builder();

    MPoint delta, currPt, newPt;
    unsigned int i=0;
    unsigned int len = componentList.length();
    unsigned int cacheIndex = 0;
    unsigned int cacheLen = (NULL != pointCache) ? pointCache->length() : 0;

    if (cachingMode == MPxSurfaceShape::kRestorePoints) {
        // restore points from the pointCache
        //
        if (len > 0) {
            // traverse the component list
            //
            for ( i=0; i<len; i++ )
            {
                MObject comp = componentList[i];
                MFnSingleIndexedComponent fnComp( comp );
                int elemCount = fnComp.elementCount();
                for ( int idx=0; idx<elemCount && cacheIndex < cacheLen; idx++, cacheIndex++) {
                    int elemIndex = fnComp.element( idx );
                    double3 & pt = builder.addElement( elemIndex ).asDouble3();
                    MPoint& cachePt = (*pointCache)[cacheIndex];
                    pt[0] += cachePt.x;
                    pt[1] += cachePt.y;
                    pt[2] += cachePt.z;
                }
            }
        } else {
            // if the component list is of zero-length, it indicates that we
            // should transform the entire surface
            //
            len = geomPtr->vertices.length();
            for ( unsigned int idx = 0; idx < len && idx < cacheLen; ++idx ) {
                double3 & pt = builder.addElement( idx ).asDouble3();
                MPoint& cachePt = (*pointCache)[cacheIndex];
                pt[0] += cachePt.x;
                pt[1] += cachePt.y;
                pt[2] += cachePt.z;
            }
        }
    } else {
        // Tweak the points. If savePoints is true, also save the tweaks in the
        // pointCache. If updatePoints is true, add the new tweaks to the existing
        // data in the pointCache.
        //
        if (len > 0) {
            for ( i=0; i<len; i++ )
            {
                MObject comp = componentList[i];
                MFnSingleIndexedComponent fnComp( comp );
                int elemCount = fnComp.elementCount();
                if (savePoints) {
                    pointCache->setSizeIncrement(elemCount);
                }
                for ( int idx=0; idx<elemCount; idx++ )
                {
                    int elemIndex = fnComp.element( idx );
                    double3 & pt = builder.addElement( elemIndex ).asDouble3();
                    currPt = newPt = geomPtr->vertices[elemIndex];
                    newPt *= mat;
                    delta.x = newPt.x - currPt.x;
                    delta.y = newPt.y - currPt.y;
                    delta.z = newPt.z - currPt.z;
                    pt[0] += delta.x;
                    pt[1] += delta.y;
                    pt[2] += delta.z;
                    if (savePoints) {
                        // store the points in the pointCache for undo
                        //
                        pointCache->append(delta*(-1.0));
                    } else if (updatePoints && cacheIndex < cacheLen) {
                        MPoint& cachePt = (*pointCache)[cacheIndex];
                        cachePt[0] -= delta.x;
                        cachePt[1] -= delta.y;
                        cachePt[2] -= delta.z;
                        cacheIndex++;
                    }
                }
            }
        } else {
            // if the component list is of zero-length, it indicates that we
            // should transform the entire surface
            //
            len = geomPtr->vertices.length();
            if (savePoints) {
                pointCache->setSizeIncrement(len);
            }
            for ( unsigned int idx = 0; idx < len; ++idx ) {
                double3 & pt = builder.addElement( idx ).asDouble3();
                currPt = newPt = geomPtr->vertices[idx];
                newPt *= mat;
                delta.x = newPt.x - currPt.x;
                delta.y = newPt.y - currPt.y;
                delta.z = newPt.z - currPt.z;
                pt[0] += delta.x;
                pt[1] += delta.y;
                pt[2] += delta.z;
                if (savePoints) {
                    // store the points in the pointCache for undo
                    //
                    pointCache->append(delta*-1.0);
                } else if (updatePoints && idx < cacheLen) {
                    MPoint& cachePt = (*pointCache)[idx];
                    cachePt[0] -= delta.x;
                    cachePt[1] -= delta.y;
                    cachePt[2] -= delta.z;
                }
            }
        }
    }
    // Set the builder into the handle.
    //
    handle.set(builder);

    // Tell maya the bounding box for this object has changed
    // and thus "boundingBox()" needs to be called.
    //
    childChanged( MPxSurfaceShape::kBoundingBoxChanged );
}


/* override */
//
// Description
//
//    Returns offsets for the given components to be used my the
//    move tool in normal/u/v mode.
//
// Arguments
//
//    component - components to calculate offsets for
//    direction - array of offsets to be filled
//    mode      - the type of offset to be calculated
//    normalize - specifies whether the offsets should be normalized
//
// Returns
//
//    true if the offsets could be calculated, false otherwise
//
bool apiMesh::vertexOffsetDirection( MObject & component,
                                     MVectorArray & direction,
                                     MVertexOffsetMode mode,
                                     bool normalize )
{
    MStatus stat;
    bool offsetOkay = false ;

    MFnSingleIndexedComponent fnComp( component, &stat );
    if ( !stat || (component.apiType() != MFn::kMeshVertComponent) ) {
        return false;
    }

    offsetOkay = true ;

    apiMeshGeom * geomPtr = meshGeom();
    if ( NULL == geomPtr ) {
        return false;
    }

    // For each vertex add the appropriate offset
    //
    int count = fnComp.elementCount();
    for ( int idx=0; idx<count; idx++ )
    {
        MVector normal = geomPtr->normals[ fnComp.element(idx) ];

        if( mode == MPxSurfaceShape::kNormal ) {
            if( normalize ) normal.normalize() ;
            direction.append( normal );
        }
        else {
            // Construct an orthonormal basis from the normal
            // uAxis, and vAxis are the new vectors.
            //
            MVector uAxis, vAxis ;
            int    i, j, k;
            double a;
            normal.normalize();

            i = 0;  a = fabs( normal[0] );
            if ( a < fabs(normal[1]) ) { i = 1; a = fabs(normal[1]); }
            if ( a < fabs(normal[2]) ) i = 2;
            j = (i+1)%3;  k = (j+1)%3;
            a = sqrt(normal[i]*normal[i] + normal[j]*normal[j]);
            uAxis[i] = -normal[j]/a; uAxis[j] = normal[i]/a; uAxis[k] = 0.0;
            vAxis = normal^uAxis;

            if ( mode == MPxSurfaceShape::kUTangent ||
                 mode == MPxSurfaceShape::kUVNTriad )
            {
                if( normalize ) uAxis.normalize() ;
                direction.append( uAxis );
            }

            if ( mode == MPxSurfaceShape::kVTangent ||
                 mode == MPxSurfaceShape::kUVNTriad )
            {
                if( normalize ) vAxis.normalize() ;
                direction.append( vAxis );
            }

            if ( mode == MPxSurfaceShape::kUVNTriad ) {
                if( normalize ) normal.normalize() ;
                direction.append( normal );
            }
        }
    }

    return offsetOkay ;
}

/* override */
bool apiMesh::isBounded() const
//
// Description
//
//    Specifies that this object has a boundingBox.
//
{
    return true;
}

/* override */
MBoundingBox apiMesh::boundingBox() const
//
// Description
//
//    Returns the bounding box for this object.
//    It is a good idea not to recompute here as this funcion is called often.
//
{
    MObject thisNode = thisMObject();
    MPlug   c1Plug( thisNode, bboxCorner1 );
    MPlug   c2Plug( thisNode, bboxCorner2 );
    MObject corner1Object;
    MObject corner2Object;
    c1Plug.getValue( corner1Object );
    c2Plug.getValue( corner2Object );

    double3 corner1, corner2;

    MFnNumericData fnData;
    fnData.setObject( corner1Object );
    fnData.getData( corner1[0], corner1[1], corner1[2] );
    fnData.setObject( corner2Object );
    fnData.getData( corner2[0], corner2[1], corner2[2] );

    MPoint corner1Point( corner1[0], corner1[1], corner1[2] );
    MPoint corner2Point( corner2[0], corner2[1], corner2[2] );

    return MBoundingBox( corner1Point, corner2Point );
}

/* override */
MPxGeometryIterator* apiMesh::geometryIteratorSetup(MObjectArray& componentList,
                                                    MObject& components,
                                                    bool forReadOnly )
//
// Description
//
//    Creates a geometry iterator compatible with his shape.
//
// Arguments
//
//    componentList - list of components to be iterated
//    components    - component to be iterator
//    forReadOnly   -
//
// Returns
//
//    An iterator for the components
//
{
    apiMeshGeomIterator * result = NULL;
    if ( components.isNull() ) {
        result = new apiMeshGeomIterator( meshGeom(), componentList );
    }
    else {
        result = new apiMeshGeomIterator( meshGeom(), components );
    }
    return result;
}

/* override */
bool apiMesh::acceptsGeometryIterator( bool writeable )
//
// Description
//
//    Specifies that this shape can provide an iterator for getting/setting
//    control point values.
//
// Arguments
//
//    writable - maya asks for an iterator that can set points if this is true
//
{
    return true;
}

/* override */
bool apiMesh::acceptsGeometryIterator( MObject&, bool writeable,
                                       bool forReadOnly )
//
// Description
//
//    Specifies that this shape can provide an iterator for getting/setting
//    control point values.
//
// Arguments
//
//    writable   - maya asks for an iterator that can set points if this is true
//    forReadOnly - maya asking for an iterator for querying only
//
{
    return true;
}


//
// Helper functions
//

bool apiMesh::hasHistory()
//
// Description
//
//    Returns true if the shape has input history, false otherwise.
//
{
    return fHasHistoryOnCreate;
}

MStatus apiMesh::computeBoundingBox( MDataBlock& datablock )
//
// Description
//
//    Use the larges/smallest vertex positions to set the corners
//    of the bounding box.
//
{
    MStatus stat = MS::kSuccess;

    // Update bounding box
    //
    MDataHandle lowerHandle = datablock.outputValue( bboxCorner1 );
    MDataHandle upperHandle = datablock.outputValue( bboxCorner2 );
    double3 &lower = lowerHandle.asDouble3();
    double3 &upper = upperHandle.asDouble3();

    apiMeshGeom* geomPtr = meshGeom();
    int cnt = geomPtr->vertices.length();
    if ( cnt == 0 ) return stat;

    // This clears any old bbox values
    //
    MPoint tmppnt = geomPtr->vertices[0];
    lower[0] = tmppnt[0]; lower[1] = tmppnt[1]; lower[2] = tmppnt[2];
    upper[0] = tmppnt[0]; upper[1] = tmppnt[1]; upper[2] = tmppnt[2];


    for ( int i=0; i<cnt; i++ )
    {
        MPoint pnt = geomPtr->vertices[i];

        if ( pnt[0] < lower[0] ) lower[0] = pnt[0];
        if ( pnt[1] < lower[1] ) lower[1] = pnt[1];
        if ( pnt[2] > lower[2] ) lower[2] = pnt[2];
        if ( pnt[0] > upper[0] ) upper[0] = pnt[0];
        if ( pnt[1] > upper[1] ) upper[1] = pnt[1];
        if ( pnt[2] < upper[2] ) upper[2] = pnt[2];
    }

    lowerHandle.setClean();
    upperHandle.setClean();

    // Signal that the bounding box has changed.
    //
    childChanged( MPxSurfaceShape::kBoundingBoxChanged );

    return stat;
}

MStatus apiMesh::computeInputSurface( const MPlug& plug, MDataBlock& datablock )
//
// Description
//
//    If there is input history, evaluate the input attribute
//
{
    MStatus stat = MS::kSuccess;

    // Get the input surface if there is history
    //
    if ( hasHistory() ) {
        MDataHandle inputHandle = datablock.inputValue( inputSurface, &stat );
        MCHECKERROR( stat, "computeInputSurface error getting inputSurface")

            apiMeshData* surf = (apiMeshData*) inputHandle.asPluginData();
        if ( NULL == surf ) {
            cerr << "NULL inputSurface data found\n";
            return stat;
        }

        apiMeshGeom* geomPtr = surf->fGeometry;

        // Create the cachedSurface and copy the input surface into it
        //
        MFnPluginData fnDataCreator;
        MTypeId tmpid( apiMeshData::id );
        fnDataCreator.create( tmpid, &stat );
        MCHECKERROR( stat, "compute : error creating Cached apiMeshData")
            apiMeshData * newCachedData = (apiMeshData*)fnDataCreator.data( &stat );
        MCHECKERROR( stat, " error gettin proxy cached apiMeshData object")
            *(newCachedData->fGeometry) = *geomPtr;

        MDataHandle cachedHandle = datablock.outputValue( cachedSurface,&stat );
        MCHECKERROR( stat, "computeInputSurface error getting cachedSurface")
            cachedHandle.set( newCachedData );
    }
    return stat;
}

MStatus apiMesh::computeOutputSurface( const MPlug& plug,
                                       MDataBlock& datablock )
//
// Description
//
//    Compute the outputSurface attribute.
//
//    If there is no history, use cachedSurface as the
//    input surface. All tweaks will get written directly
//    to it. Output is just a copy of the cached surface
//    that can be connected etc.
//
{
    MStatus stat;

    // Check for an input surface. The input surface, if it
    // exists, is copied to the cached surface.
    //
    if ( ! computeInputSurface( plug, datablock ) ) {
        return MS::kFailure;
    }

    // Get a handle to the cached data
    //
    MDataHandle cachedHandle = datablock.outputValue( cachedSurface, &stat );
    MCHECKERROR( stat, "computeInputSurface error getting cachedSurface")
    apiMeshData* cached = (apiMeshData*) cachedHandle.asPluginData();
    if ( NULL == cached ) {
        cerr << "NULL cachedSurface data found\n";
    }

    datablock.setClean( plug );

    // Apply any vertex offsets.
    //
    if ( hasHistory() ) {
        applyTweaks( datablock, cached->fGeometry );
    }
    else {
        MArrayDataHandle cpHandle = datablock.inputArrayValue( mControlPoints,
                                                           &stat );
        cpHandle.setAllClean();
    }

    // Create some output data

    //
    MFnPluginData fnDataCreator;
    MTypeId tmpid( apiMeshData::id );
    fnDataCreator.create( tmpid, &stat );
    MCHECKERROR( stat, "compute : error creating apiMeshData")
    apiMeshData * newData = (apiMeshData*)fnDataCreator.data( &stat );
    MCHECKERROR( stat, "compute : error gettin at proxy apiMeshData object")

    // Copy the data
    //
    if ( NULL != cached ) {
        *(newData->fGeometry) = *(cached->fGeometry);
    }
    else {
        cerr << "computeOutputSurface: NULL cachedSurface data\n";
    }

    // Assign the new data to the outputSurface handle
    //
    MDataHandle outHandle = datablock.outputValue( outputSurface );
    outHandle.set( newData );

    // Update the bounding box attributes
    //
    stat = computeBoundingBox( datablock );
    MCHECKERROR( stat, "computeBoundingBox" )

    return stat;
}

MStatus apiMesh::computeWorldSurface( const MPlug& plug, MDataBlock& datablock )
//
// Description
//
//    Compute the worldSurface attribute.
//
{
    MStatus stat;

    computeOutputSurface( plug, datablock );
    MDataHandle inHandle = datablock.outputValue( outputSurface );
    apiMeshData* outSurf = (apiMeshData*)inHandle.asPluginData();
    if ( NULL == outSurf ) {
        cerr << "computeWorldSurface: outSurf NULL\n";
        return MS::kFailure;
    }

    // Create some output data
    //
    MFnPluginData fnDataCreator;
    MTypeId tmpid( apiMeshData::id );

    fnDataCreator.create( tmpid, &stat );
    MCHECKERROR( stat, "compute : error creating apiMeshData")

    apiMeshData * newData = (apiMeshData*)fnDataCreator.data( &stat );
    MCHECKERROR( stat, "compute : error gettin at proxy apiMeshData object")

    // Get worldMatrix from MPxSurfaceShape and set it to MPxGeometryData
    MMatrix worldMat = getWorldMatrix(datablock, 0);
    newData->setMatrix( worldMat );

    // Copy the data
    //
    *(newData->fGeometry) = *(outSurf->fGeometry);

    // Assign the new data to the outputSurface handle
    //
    int arrayIndex = plug.logicalIndex( &stat );
    MCHECKERROR( stat, "computWorldSurface : logicalIndex" );

    MArrayDataHandle worldHandle = datablock.outputArrayValue( worldSurface,
                                                               &stat );
    MCHECKERROR( stat, "computWorldSurface : outputArrayValue" );

    MArrayDataBuilder builder = worldHandle.builder( &stat );
    MCHECKERROR( stat, "computWorldSurface : builder" );

    MDataHandle outHandle = builder.addElement( arrayIndex, &stat );
    MCHECKERROR( stat, "computWorldSurface : addElement" );

    outHandle.set( newData );

    return stat;
}



MStatus apiMesh::applyTweaks( MDataBlock& datablock, apiMeshGeom* geomPtr )
//
// Description
//
//    If the shape has history, apply any tweaks (offsets) made
//    to the control points.
//
{
    MStatus stat;

    MArrayDataHandle cpHandle = datablock.inputArrayValue( mControlPoints,
                                                           &stat );
    MCHECKERROR( stat, "applyTweaks get cpHandle" )

    // Loop through the component list and transform each vertex.
    //
    int elemCount = cpHandle.elementCount();
    for ( int idx=0; idx<elemCount; idx++ )
    {
        int elemIndex = cpHandle.elementIndex();
        MDataHandle pntHandle = cpHandle.outputValue();
        double3& pnt = pntHandle.asDouble3();
        MPoint offset( pnt[0], pnt[1], pnt[2] );

        // Apply the tweaks to the output surface
        //
        MPoint& oldPnt = geomPtr->vertices[elemIndex];
        oldPnt = oldPnt + offset;

        cpHandle.next();
    }

    return stat;
}

bool apiMesh::value( int pntInd, int vlInd, double & val ) const
//
// Description
//
//    Helper function to return the value of a given vertex
//    from the cachedMesh.
//
{
    bool result = false;

    apiMesh* nonConstThis = (apiMesh*)this;
    apiMeshGeom* geomPtr = nonConstThis->cachedGeom();
    if ( NULL != geomPtr ) {
        MPoint point = geomPtr->vertices[ pntInd ];
        val = point[ vlInd ];
        result = true;
    }

    return result;
}

bool apiMesh::value( int pntInd, MPoint & val ) const
//
// Description
//
//    Helper function to return the value of a given vertex
//    from the cachedMesh.
//
{
    bool result = false;

    apiMesh* nonConstThis = (apiMesh*)this;
    apiMeshGeom* geomPtr = nonConstThis->cachedGeom();
    if ( NULL != geomPtr ) {
        MPoint point = geomPtr->vertices[ pntInd ];
        val = point;
        result = true;
    }

    return result;
}

bool apiMesh::setValue( int pntInd, int vlInd, double val )
//
// Description
//
//    Helper function to set the value of a given vertex
//    in the cachedMesh.
//
{
    bool result = false;

    apiMesh* nonConstThis = (apiMesh*)this;
    apiMeshGeom* geomPtr = nonConstThis->cachedGeom();
    if ( NULL != geomPtr ) {
        MPoint& point = geomPtr->vertices[ pntInd ];
        point[ vlInd ] = val;
        result = true;
    }

    verticesUpdated();

    return result;
}

bool apiMesh::setValue( int pntInd, const MPoint & val )
//
// Description
//
//    Helper function to set the value of a given vertex
//    in the cachedMesh.
//
{
    bool result = false;

    apiMesh* nonConstThis = (apiMesh*)this;
    apiMeshGeom* geomPtr = nonConstThis->cachedGeom();
    if ( NULL != geomPtr ) {
        geomPtr->vertices[ pntInd ] = val;
        result = true;
    }

    verticesUpdated();

    return result;
}

MObject apiMesh::meshDataRef()
//
// Description
//
//    Get a reference to the mesh data (outputSurface)
//    from the datablock. If dirty then an evaluation is
//    triggered.
//
{
    // Get the datablock for this node
    //
    MDataBlock datablock = forceCache();

    // Calling inputValue will force a recompute if the
    // connection is dirty. This means the most up-to-date
    // mesh data will be returned by this method.
    //
    MDataHandle handle = datablock.inputValue( outputSurface );
    return handle.data();
}

apiMeshGeom* apiMesh::meshGeom()
//
// Description
//
//    Returns a pointer to the apiMeshGeom underlying the shape.
//
{
    MStatus stat;
    apiMeshGeom * result = NULL;

    MObject tmpObj = meshDataRef();
    MFnPluginData fnData( tmpObj );
    apiMeshData * data = (apiMeshData*)fnData.data( &stat );
    MCHECKERRORNORET( stat, "meshGeom : Failed to get apiMeshData");

    if ( NULL != data ) {
        result = data->fGeometry;
    }

    return result;
}

MObject apiMesh::cachedDataRef()
//
// Description
//
//    Get a reference to the mesh data (cachedSurface)
//    from the datablock. No evaluation is triggered.
//
{
    // Get the datablock for this node
    //
    MDataBlock datablock = forceCache();
    MDataHandle handle = datablock.outputValue( cachedSurface );
    return handle.data();
}

apiMeshGeom* apiMesh::cachedGeom()
//
// Description
//
//    Returns a pointer to the apiMeshGeom underlying the shape.
//
{
    MStatus stat;
    apiMeshGeom * result = NULL;

    MObject tmpObj = cachedDataRef();
    MFnPluginData fnData( tmpObj );
    apiMeshData * data = (apiMeshData*)fnData.data( &stat );
    MCHECKERRORNORET( stat, "cachedGeom : Failed to get apiMeshData");

    if ( NULL != data ) {
        result = data->fGeometry;
    }

    return result;
}

MStatus apiMesh::buildControlPoints( MDataBlock& datablock, int count )
//
// Description
//
//    Check the controlPoints array. If there is input history
//    then we will use this array to store tweaks (vertex movements).
//
{
    MStatus stat;

    MArrayDataHandle cpH = datablock.outputArrayValue( mControlPoints, &stat );
    MCHECKERROR( stat, "compute get cpH" )

    MArrayDataBuilder oldBuilder = cpH.builder();
    if ( count != (int)oldBuilder.elementCount() )
    {
        // Make and set the new builder based on the
        // info from the old builder.
        MArrayDataBuilder builder( oldBuilder );
        MCHECKERROR( stat, "compute - create builder" )

        for ( int vtx=0; vtx<count; vtx++ )
        {
          /* double3 & pt = */ builder.addElement( vtx ).asDouble3();
        }

        cpH.set( builder );
    }

    cpH.setAllClean();

    return stat;
}

void apiMesh::verticesUpdated()
//
// Description
//
//    Helper function to tell maya that this shape's
//    vertices have updated and that the bbox needs
//    to be recalculated and the shape redrawn.
//
{
    childChanged( MPxSurfaceShape::kBoundingBoxChanged );
    childChanged( MPxSurfaceShape::kObjectChanged );
}

void* apiMesh::creator()
//
// Description
//
//    Called internally to create a new instance of the users MPx node.
//
{
    return new apiMesh();
}

MStatus apiMesh::initialize()
//
// Description
//
//    Attribute (static) initialization.
//    See api_macros.h.
//
{
    MStatus             stat;
    MFnTypedAttribute   typedAttr;

    // ----------------------- INPUTS --------------------------
    inputSurface = typedAttr.create( "inputSurface", "is",
                                      apiMeshData::id,
                                      MObject::kNullObj, &stat );
    MCHECKERROR( stat, "create inputSurface attribute" )
    typedAttr.setStorable( false );
    ADD_ATTRIBUTE( inputSurface );

    // ----------------------- OUTPUTS -------------------------

    // bbox attributes
    //
    MAKE_NUMERIC_ATTR(  bboxCorner1, "bboxCorner1", "bb1",
                        MFnNumericData::k3Double, 0,
                        false, false, false );
    MAKE_NUMERIC_ATTR(  bboxCorner2, "bboxCorner2", "bb2",
                        MFnNumericData::k3Double, 0,
                        false, false, false );

    // local/world output surface attributes
    //
    outputSurface = typedAttr.create( "outputSurface", "os",
                                      apiMeshData::id,
                                      MObject::kNullObj, &stat );
    MCHECKERROR( stat, "create outputSurface attribute" )
    ADD_ATTRIBUTE( outputSurface );
    typedAttr.setWritable( false );

    worldSurface = typedAttr.create( "worldSurface", "ws",
                                      apiMeshData::id,
                                      MObject::kNullObj, &stat );
    MCHECKERROR( stat, "create worldSurface attribute" );

    typedAttr.setCached( false );

    typedAttr.setWritable( false );

    stat = typedAttr.setArray( true );
    MCHECKERROR( stat, "set array" );

    stat = typedAttr.setUsesArrayDataBuilder( true );
    MCHECKERROR( stat, "set uses array data builder" );

    stat = typedAttr.setDisconnectBehavior( MFnAttribute::kDelete );
    MCHECKERROR( stat, "set disconnect behavior data builder" );

    stat = typedAttr.setWorldSpace( true );
    MCHECKERROR( stat, "set world space" );

    ADD_ATTRIBUTE( worldSurface );

    // Cached surface used for file IO
    //
    cachedSurface = typedAttr.create( "cachedSurface", "cs",
                                      apiMeshData::id,
                                      MObject::kNullObj, &stat );
    MCHECKERROR( stat, "create cachedSurface attribute" )
    typedAttr.setReadable( true );
    typedAttr.setWritable( true );
    typedAttr.setStorable( true );
    ADD_ATTRIBUTE( cachedSurface );

    // ---------- Specify what inputs affect the outputs ----------
    //
    ATTRIBUTE_AFFECTS( inputSurface, outputSurface );
    ATTRIBUTE_AFFECTS( inputSurface, worldSurface );
    ATTRIBUTE_AFFECTS( outputSurface, worldSurface );
    ATTRIBUTE_AFFECTS( inputSurface, bboxCorner1 );
    ATTRIBUTE_AFFECTS( inputSurface, bboxCorner2 );
    ATTRIBUTE_AFFECTS( cachedSurface, outputSurface );
    ATTRIBUTE_AFFECTS( cachedSurface, worldSurface );


    ATTRIBUTE_AFFECTS( mControlPoints, outputSurface );
    ATTRIBUTE_AFFECTS( mControlValueX, outputSurface );
    ATTRIBUTE_AFFECTS( mControlValueY, outputSurface );
    ATTRIBUTE_AFFECTS( mControlValueZ, outputSurface );
    ATTRIBUTE_AFFECTS( mControlPoints, cachedSurface );
    ATTRIBUTE_AFFECTS( mControlValueX, cachedSurface );
    ATTRIBUTE_AFFECTS( mControlValueY, cachedSurface );
    ATTRIBUTE_AFFECTS( mControlValueZ, cachedSurface );
    ATTRIBUTE_AFFECTS( mControlPoints, worldSurface );
    ATTRIBUTE_AFFECTS( mControlValueX, worldSurface );
    ATTRIBUTE_AFFECTS( mControlValueY, worldSurface );
    ATTRIBUTE_AFFECTS( mControlValueZ, worldSurface );


    return MS::kSuccess;
}

//
// Node registry
//
// Registers/Deregisters apiMeshData geometry data,
// apiMeshCreator DG node, and apiMeshShape user defined shape.
//

MStatus initializePlugin( MObject obj )
{
    MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");
    MStatus stat1, stat2, stat3, stat4;

    stat1 = plugin.registerData( "apiMeshData", apiMeshData::id,
                                  &apiMeshData::creator,
                                  MPxData::kGeometryData );
    if ( ! stat1 ) {
        cerr << "Failed to register geometry data : apiMeshData \n";
        return stat1;
    }

    stat2 = plugin.registerShape( "apiMesh", apiMesh::id,
                                   &apiMesh::creator,
                                   &apiMesh::initialize,
                                   &apiMeshUI::creator,
                                   &apiMesh::drawDbClassification );
    if ( ! stat2 ) {
        cerr << "Failed to register shape\n";
        if ( stat1) plugin.deregisterData( apiMeshData::id );
        return stat2;
    }

    stat3 = plugin.registerNode( "apiMeshCreator", apiMeshCreator::id,
                                 &apiMeshCreator::creator,
                                 &apiMeshCreator::initialize  );
    if ( ! stat3 ) {
        cerr << "Failed to register creator\n";
        if ( stat2 ) {
            plugin.deregisterNode( apiMesh::id );
            plugin.deregisterData( apiMeshData::id );
        }
    }

    stat4 = MHWRender::MDrawRegistry::registerGeometryOverrideCreator(
                apiMesh::drawDbClassification,
                apiMesh::drawRegistrantId,
                apiMeshGeometryOverride::Creator);
    if ( ! stat4 ) {
        cerr << "Failed to register Viewport 2.0 geometry override\n";
    }

    return stat3;

}

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

    stat = MHWRender::MDrawRegistry::deregisterGeometryOverrideCreator(
                apiMesh::drawDbClassification,
                apiMesh::drawRegistrantId);
    if ( ! stat ) {
        cerr << "Failed to deregister geometry override : apiMeshGeometryOverride \n";
    }

    stat = plugin.deregisterNode( apiMesh::id );
    if ( ! stat ) {
        cerr << "Failed to deregister shape : apiMeshShape \n";
    }

    stat = plugin.deregisterData( apiMeshData::id );
    if ( ! stat ) {
        cerr << "Failed to deregister geometry data : apiMeshData \n";
    }

    stat = plugin.deregisterNode( apiMeshCreator::id );
    if ( ! stat ) {
        cerr << "Failed to deregister node : apiMeshCreator \n";
    }

    return stat;
}