//- // ========================================================================== // Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors. All // rights reserved. // // The coded instructions, statements, computer programs, and/or related // material (collectively the "Data") in these files contain unpublished // information proprietary to Autodesk, Inc. ("Autodesk") and/or its // licensors, which is protected by U.S. and Canadian federal copyright // law and by international treaties. // // The Data is provided for use exclusively by You. You have the right // to use, modify, and incorporate this Data into other products for // purposes authorized by the Autodesk software license agreement, // without fee. // // The copyright notices in the Software and this entire statement, // including the above license grant, this restriction and the // following disclaimer, must be included in all copies of the // Software, in whole or in part, and all derivative works of // the Software, unless such copies or derivative works are solely // in the form of machine-executable object code generated by a // source language processor. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. // AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED // WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF // NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR // PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR // TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS // BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, // DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK // AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY // OR PROBABILITY OF SUCH DAMAGES. // // ========================================================================== //+ /////////////////////////////////////////////////////////////////////////////// // // apiMeshShape.cpp // //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool debug = false; //////////////////////////////////////////////////////////////////////////////// // // Shape implementation // //////////////////////////////////////////////////////////////////////////////// MObject apiMesh::inputSurface; MObject apiMesh::outputSurface; MObject apiMesh::cachedSurface; MObject apiMesh::worldSurface; MObject apiMesh::bboxCorner1; MObject apiMesh::bboxCorner2; 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 // // 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; iivertices[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; idxvertices[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; isetSizeIncrement(elemCount); } for ( int idx=0; idxappend(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; ifGeometry->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; ivertices.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; isetSizeIncrement(elemCount); } for ( int idx=0; idxvertices[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; idxnormals[ 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; ivertices[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; idxvertices[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