- Main Page
- Related Pages
- Modules
- Classes
- Examples

# 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;
    }