/******************************************************************************/ /*! HairTangents Copyright 2008 Electronic Arts Inc. \file hairTangents.cpp \brief Code file for Hair Tangents plugin class. */ /******************************************************************************/ /*** Includes *****************************************************************/ #include "hairTangents.h" #include #include #include #include #include #include #include #include /*** Using ********************************************************************/ using namespace hairTangents; /*** Macros *******************************************************************/ #define lerp(t, a, b) ( a + t * (b - a) ) /*** Interface ****************************************************************/ /*** Variables ****************************************************************/ /*** Implementation ***********************************************************/ namespace hairTangents { /******************************************************************************/ /*! cHairTangents :: creator \brief [Put your brief description here.] \param - none. \return - [Describe the 'void *' return result here.] [Put your optional additional description here.] */ /******************************************************************************/ void * cHairTangents::creator() { return new cHairTangents; } /******************************************************************************/ /*! cHairTangents :: isUndoable \brief [Put your brief description here.] \param - none. \return - [Describe the 'bool' return result here.] [Put your optional additional description here.] */ /******************************************************************************/ bool cHairTangents::isUndoable() const { return true; } /******************************************************************************/ /*! cHairTangents :: newSyntax \brief [Put your brief description here.] \param - none. \return - [Describe the 'MSyntax' return result here.] [Put your optional additional description here.] */ /******************************************************************************/ MSyntax cHairTangents::newSyntax() { MSyntax syntax; CHECK_MSTATUS( syntax.addFlag( "v" , "verbose" , MSyntax::kBoolean ) ); CHECK_MSTATUS( syntax.addFlag( "t" , "tolerance" , MSyntax::kDouble ) ); return syntax; } /******************************************************************************/ /*! cHairTangents :: doIt \brief [Put your brief description here.] \param args - [Put the description of the parameter 'args' here] \return - [Describe the 'MStatus' return result here.] [Put your optional additional description here.] */ /******************************************************************************/ MStatus cHairTangents::doIt(const MArgList & args) { //usage info MString usage = "hairTangents usage:\n"; usage += "-v -verbose: (Bool) Turns plugin verbosity on or off. On by default."; usage += "-t -tolerance: (Double) Search radius for closest point on curve. 0.001 by default"; MSyntax syntax = newSyntax(); MStatus status; //parse args MArgParser argParser( syntax , args , &status ); if( !status ) { MGlobal::displayError( usage ); return status; } //verbosity if( argParser.isFlagSet( "v" , &status ) ) { argParser.getFlagArgument( "v", 0 , bVerbose ); if( bVerbose ) { MGlobal::displayInfo( "Setting verbosity flag to true." ); } } else { bVerbose = true; MGlobal::displayInfo( "Verbosity on by default." ); } //tolerance if( argParser.isFlagSet( "t" , &status ) ) { argParser.getFlagArgument( "t", 0 , dTolerance ); if( bVerbose ) { MString msTolerance; msTolerance.set(dTolerance); MGlobal::displayInfo( "Setting tolerance flag to \"" + msTolerance + "\"." ); } } else { dTolerance = 0.001f; MGlobal::displayInfo( "Defaulting tolerance to 0.001." ); } return redoIt(); } void print(MString text) { MString txt; txt += text; //MGlobal::displayInfo(txt ); } /******************************************************************************/ /*! cHairTangents :: redoIt \brief [Put your brief description here.] \param - none. \return - [Describe the 'MStatus' return result here.] [Put your optional additional description here.] */ /******************************************************************************/ MStatus cHairTangents::redoIt() { //Gotta have an MStatus! MStatus status; //Get all selections MSelectionList selection; CHECK_MSTATUS( MGlobal::getActiveSelectionList( selection ) ); //Sanity check: //There must be at least 2 items in the selection list, a mesh and a curve. if( selection.length() < 2) { MGlobal::displayError( "Please have at least one hair mesh and one NURBS tangent curve selected." ); return MStatus::kFailure; } //Break selection down into mesh and nurbs curves for( uint i = 0 ; i < selection.length() ; i++ ) { MDagPath thisPath; CHECK_MSTATUS( selection.getDagPath( i , thisPath , MObject::kNullObj ) ); if( thisPath.hasFn( MFn::kMesh ) ) { //mesh CHECK_MSTATUS( mMeshes.append( thisPath ) ); continue; } if( thisPath.hasFn( MFn::kNurbsCurve ) ) { //nurbs curve CHECK_MSTATUS( mCurves.append( thisPath ) ); continue; } } //Quick sanity check: //Make sure we have at least one mesh and one curve if( mMeshes.length() == 0) { MGlobal::displayError( "Please have at least one hair mesh selected." ); return MStatus::kFailure; } if( mCurves.length() == 0 ) { MGlobal::displayError( "Please have at least one NURBS tangent curve selected." ); return MStatus::kFailure; } //let's do this! MComputation computation; computation.beginComputation(); //for each mesh... for( uint iMesh = 0 ; iMesh < mMeshes.length() ; iMesh++ ) { //get mesh function set MFnMesh meshFn( mMeshes[ iMesh ] , &status ); CHECK_MSTATUS( status ); //make sure this mesh actually has vertex colors int numberOfColorSets = meshFn.numColorSets( &status ); CHECK_MSTATUS( status ); if( ! numberOfColorSets ) { MGlobal::displayWarning ( "Mesh " + meshFn.name() + " has no color sets! Skipping this mesh!" ); continue; } //get current color set and inform user MString currentColorSetName = meshFn.currentColorSetName( &status ); CHECK_MSTATUS( status ); MGlobal::displayInfo( "Setting colors on mesh \"" + meshFn.name() + "\" color set \"" + currentColorSetName + "\"." ); MColorArray vertColors; meshFn.getVertexColors(vertColors); //get each vert... MItMeshVertex meshIt( mMeshes[ iMesh ] , MObject::kNullObj , &status ); CHECK_MSTATUS( status ); for( ; ! meshIt.isDone() ; meshIt.next() ) { //store points and params for all the nurbs curves MPointArray closestPoints; MDoubleArray params; //track best and next best fit and the distance from point to point uint bestFit = 0; uint nextBestFit = 0; double closestDistance = 1000000; //really big number double nextClosestDistance = closestDistance; //get this vert's position MPoint thisPoint( meshIt.position() ); //find closest points and params to this point for( uint iCurve = 0 ; iCurve < mCurves.length() ; iCurve++ ) { //get closest point on all curves //use the closest point & param for 2 closest points MFnNurbsCurve curveFn(mCurves[ iCurve ] , &status ); CHECK_MSTATUS( status ); //get closest point & param on this curve to this point double param = 0; MPoint point = curveFn.closestPoint( thisPoint , ¶m , dTolerance, MSpace::kWorld, &status ); CHECK_MSTATUS( status ); CHECK_MSTATUS( params.append( param ) ); //see if this point is closer that any others double thisDistance = thisPoint.distanceTo(point); if( thisDistance < closestDistance ) { //This point is closer. Update closest distance and this curve's index, bumping the //prevoius closest curve index to the number 2 spot nextClosestDistance = closestDistance; closestDistance = thisDistance; nextBestFit = bestFit; //previous closest curve index is now runner up bestFit = iCurve; //update index of the curve with closest point } } //We've found the closest and next closest points. //Construct function sets for the 2 best curves MFnNurbsCurve bestFn( mCurves[ bestFit ] , &status ); MFnNurbsCurve nextFn( mCurves[ nextBestFit ] , &status ); //Get the hit parameter for closest and next closest curves double bestParam = params[ bestFit ]; double nextParam = params[ nextBestFit ]; //get best and next best tangents at hit parameter MVector bestTangent = bestFn.tangent( bestParam , MSpace::kWorld , &status); CHECK_MSTATUS( status ); MVector nextTangent = nextFn.tangent( nextParam , MSpace::kWorld , &status); CHECK_MSTATUS( status ); //weighted lerp to get weighted blend of 2 tangents //check for divide by 0 here MVector finalTangent; if(closestDistance + nextClosestDistance == 0) // need to work on our tangent finding code { MGlobal::displayInfo( "div by zero!?!" ); finalTangent = bestTangent; } else { double blendFactor = nextClosestDistance / (closestDistance + nextClosestDistance); // finalTangent = bestTangent * blendFactor + ( 1 - blendFactor ) * nextTangent; finalTangent.x = bestTangent.x * blendFactor + ( 1 - blendFactor ) * nextTangent.x; finalTangent.y = bestTangent.y * blendFactor + ( 1 - blendFactor ) * nextTangent.y; finalTangent.z = bestTangent.z * blendFactor + ( 1 - blendFactor ) * nextTangent.z; if (meshIt.index() < 100) { print(MString("blend:") + blendFactor); } // finalTangent = bestTangent; } if (meshIt.index() < 100) { print(MString("vert:") + meshIt.index()); print(MString(" bestT:") + bestTangent.x + "," +bestTangent.y + "," + bestTangent.z); print(MString(" nextT:") + nextTangent.x + "," +nextTangent.y + "," + nextTangent.z); print(MString(" final:") + finalTangent.x + "," +finalTangent.y + "," + finalTangent.z); } finalTangent.normalize(); //Ok, so the normalized vector is in -1 to 1 space. //That's no good for a color. //We'll do this little "Arthurism" to move the vector into 0 to 1 space for(uint iVec = 0 ; iVec < 3 ; iVec ++) { finalTangent[ iVec ] = (finalTangent[ iVec ] / 2 ) + 0.5f; } if (meshIt.index() < 100) { print(MString(" color:") + finalTangent.x + "," +finalTangent.y + "," + finalTangent.z); } //Put the vector into a color MColor tangentColor( ( float ) finalTangent[ 0 ] , ( float ) finalTangent[ 1 ] , ( float ) finalTangent[ 2 ] , vertColors[meshIt.index()].a ); //Apply the color to this mesh CHECK_MSTATUS( meshFn.setVertexColor( tangentColor , meshIt.index())); } } //all done! computation.endComputation(); return MStatus::kSuccess; } }; /******************************************************************************/