//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

// 
// File: polyModifierCmd.cpp
//
// MEL Command: polyModifierCmd
//
// Author: Lonnie Li
//

//////////////
/* Includes */
//////////////

#include "polyModifierCmd.h"

// General Includes
//
#include <maya/MGlobal.h>
#include <maya/MFloatVector.h>
#include <maya/MObjectArray.h>
#include <maya/MPlugArray.h>

#include <maya/MIOStream.h>

// Function Sets
//
#include <maya/MFnDependencyNode.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnNumericData.h>


////////////
/* Macros */
////////////

// MCheckStatus (Debugging tool)
//
#ifdef _DEBUG
#	define MCheckStatus(status,message)			\
		if( MS::kSuccess != status ) {			\
			MString error("Status failed: ");	\
			error += message;					\
			MGlobal::displayError(error);		\
			return status;						\
		}
#else
#	define MCheckStatus(status,message)
#endif

// MAssert (Debugging tool)
//
#ifdef _DEBUG
#	define MAssert(state,message)					\
		if( !state ) {								\
			MString error("Assertion failed: ");	\
			error += message;						\
			MGlobal::displayError(error);			\
			return;									\
		}
#else
#	define MAssert(state,message)
#endif

// MStatusAssert (Debugging tool)
//
#ifdef _DEBUG
#	define MStatusAssert(state,message)				\
		if( !state ) {								\
			MString error("Assertion failed: ");	\
			error += message;						\
			MGlobal::displayError(error);			\
			return MS::kFailure;					\
		}
#else
#	define MStatusAssert(state,message)
#endif


////////////////////////////////////
/* polyModifierCmd Implementation */
////////////////////////////////////

polyModifierCmd::polyModifierCmd()
{
	fDagPathInitialized = false;
	fModifierNodeTypeInitialized = false;
	fModifierNodeNameInitialized = false;
}

polyModifierCmd::~polyModifierCmd()
{}

///////////////////////
// Protected Methods //
///////////////////////

MStatus polyModifierCmd::initModifierNode( MObject /* modifierNode */ )
//
//	Description:
//
//		Override this method in a derived class to set input attributes on the
//		modifier node. If not overidden, the modifier node will remain in it's
//		default state upon construction.
//
//		The argument 'MObject modifierNode', is not used by this base class
//		implementation. However, it may be used by derived classes. To avoid
//		compiler warnings of unreferenced parameters, we comment out the parameter
//		name.
//
{
	return MS::kSuccess;
}

MStatus polyModifierCmd::directModifier( MObject /* mesh */ )
//
//	Description:
//
//		Override this method in a derived class to provide an implementation for
//		directly modifying the mesh (writing on the mesh itself). This method is
//		only called in the case where history does not exist and history is turned
//		off (ie. DG operations are not desirable).
//
//		The argument 'MObject mesh', is not used by this base class implementation.
//		However, it may be used by derived classes. To avoid compiler warnings
//		of unreferenced parameters, we comment out the parameter name.
//
{
	return MS::kSuccess;
}

MStatus polyModifierCmd::doModifyPoly()
{
	MStatus status = MS::kFailure;

	if( isCommandDataValid() )
	{
		// Get the state of the polyMesh
		//
		collectNodeState();

		if( !fHasHistory && !fHasRecordHistory )
		{
			MObject meshNode = fDagPath.node();

			// Pre-process the mesh - Cache old mesh (including tweaks, if applicable)
			//
			cacheMeshData();
			cacheMeshTweaks();

			// Call the directModifier
			//
			status = directModifier( meshNode );
		}
		else
		{
			MObject modifierNode;
			createModifierNode( modifierNode );
			initModifierNode( modifierNode );
			status = connectNodes( modifierNode );
		}
	}

	return status;
}

MStatus polyModifierCmd::redoModifyPoly()
{
	MStatus status = MS::kSuccess;

	if( !fHasHistory && !fHasRecordHistory )
	{
		MObject meshNode = fDagPath.node();

		// Call the directModifier - No need to pre-process the mesh data again
		//							 since we already have it.
		//
		status = directModifier( meshNode );
	}
	else
	{
		// Call the redo on the DG and DAG modifiers
		//
		if( !fHasHistory )
		{
			fDagModifier.doIt();
		}
		status = fDGModifier.doIt();
	}

	return status;
}

MStatus polyModifierCmd::undoModifyPoly()
{
	MStatus status = MS::kSuccess;

	if( !fHasHistory && !fHasRecordHistory )
	{
		status = undoDirectModifier();
	}
	else
	{
		fDGModifier.undoIt();

		// undoCachedMesh must be called before undoTweakProcessing because 
		// undoCachedMesh copies the original mesh *without* tweaks back onto
		// the existing mesh. Any changes done before the copy will be lost.
		//
		if( !fHasHistory )
		{
			status = undoCachedMesh();
			MCheckStatus( status, "undoCachedMesh" );
			fDagModifier.undoIt();
		}

		status = undoTweakProcessing();
		MCheckStatus( status, "undoTweakProcessing" );
	}

	return status;
}

/////////////////////
// Private Methods //
/////////////////////

bool polyModifierCmd::isCommandDataValid()
{
	bool valid = true;

	// Check the validity of the DAG path
	//
	if( fDagPathInitialized )
	{
		fDagPath.extendToShape();
		if( !fDagPath.isValid()	|| fDagPath.apiType() != MFn::kMesh )
		{
			valid = false;
		}
	}
	else
	{
		valid = false;
	}

	// Check the validity of the Modifier node type/name
	//
	if( !fModifierNodeTypeInitialized && !fModifierNodeNameInitialized )
	{
		valid = false;
	}

	return valid;
}

void polyModifierCmd::collectNodeState()
{
	MStatus status;

	// Collect node state information on the given polyMeshShape
	//
	// - HasHistory (Construction History exists)
	// - HasTweaks
	// - HasRecordHistory (Construction History is turned on)
	//
	fDagPath.extendToShape();
	MObject meshNodeShape = fDagPath.node();

	MFnDependencyNode depNodeFn;
	depNodeFn.setObject( meshNodeShape );

	MPlug inMeshPlug = depNodeFn.findPlug( "inMesh" );
	fHasHistory = inMeshPlug.isConnected();

	// Tweaks exist only if the multi "pnts" attribute contains plugs
	// which contain non-zero tweak values. Use false, until proven true
	// search algorithm.
	//
	fHasTweaks = false;
	MPlug tweakPlug = depNodeFn.findPlug( "pnts" );
	if( !tweakPlug.isNull() )
	{
		// ASSERT: tweakPlug should be an array plug!
		//
		MAssert( (tweakPlug.isArray()),
				 "tweakPlug.isArray() -- tweakPlug is not an array plug" );

		MPlug tweak;
		MFloatVector tweakData;
		int i;
		int numElements = tweakPlug.numElements();

		for( i = 0; i < numElements; i++ )
		{
			tweak = tweakPlug.elementByPhysicalIndex( i, &status );
			if( status == MS::kSuccess && !tweak.isNull() )
			{
				getFloat3PlugValue( tweak, tweakData );
				if( 0 != tweakData.x ||
					0 != tweakData.y ||
					0 != tweakData.z )
				{
					fHasTweaks = true;
					break;
				}
			}
		}
	}

	int result;
	MGlobal::executeCommand( "constructionHistory -q -tgl", result );
	fHasRecordHistory = (0 != result);
}

MStatus polyModifierCmd::createModifierNode( MObject& modifierNode )
{
	MStatus status = MS::kFailure;

	if( fModifierNodeTypeInitialized || fModifierNodeNameInitialized )
	{
		if( fModifierNodeTypeInitialized )
		{
			modifierNode = fDGModifier.createNode( fModifierNodeType, &status );
		}
		else if( fModifierNodeNameInitialized )
		{
			modifierNode = fDGModifier.createNode( fModifierNodeName, &status );
		}

		// Check to make sure that we have a modifier node of the appropriate type.
		// Requires an inMesh and outMesh attribute.
		//
		MFnDependencyNode depNodeFn( modifierNode );
		MObject inMeshAttr;
		MObject outMeshAttr;
		inMeshAttr = depNodeFn.attribute( "inMesh" );
		outMeshAttr = depNodeFn.attribute( "outMesh" );

		if( inMeshAttr.isNull() || outMeshAttr.isNull() )
		{
			displayError( "Invalid Modifier Node: inMesh and outMesh attributes are required." );
			status = MS::kFailure;
		}
	}
	
	return status;
}

MStatus polyModifierCmd::processMeshNode( modifyPolyData& data )
{
	MStatus status = MS::kSuccess;

	// Declare our function sets. Use MFnDagNode here so
	// we can retrieve the parent transform.
	//
	MFnDagNode dagNodeFn;

	// Use the DAG path to retrieve our mesh shape node. 
	//
	data.meshNodeShape = fDagPath.node();
	dagNodeFn.setObject( data.meshNodeShape );

	// ASSERT: meshNodeShape node should have a parent transform!
	//
	MStatusAssert( (0 < dagNodeFn.parentCount()),
				   "0 < dagNodeFn.parentCount() -- meshNodeshape has no parent transform" );
	data.meshNodeTransform = dagNodeFn.parent(0);
	
	data.meshNodeDestPlug = dagNodeFn.findPlug( "inMesh" );
	data.meshNodeDestAttr = data.meshNodeDestPlug.attribute();

	return status;
}

MStatus polyModifierCmd::processUpstreamNode( modifyPolyData& data )
{
	MStatus status = MS::kSuccess;

	// Declare our function sets - Although dagNodeFn derives from depNodeFn, we need
	//							   both since dagNodeFn can only refer to DAG objects.
	//							   We will use depNodeFn for all times other when dealing
	//							   with the DAG.
	//
	MFnDependencyNode	depNodeFn;
	MFnDagNode			dagNodeFn;

	// Use the selected node's plug connections to find the upstream plug.
	// Since we are looking at the selected node's inMesh attribute, it will
	// always have only one connection coming in if it has history, and none
	// otherwise.
	//
	// If there is no history, copy the selected node and place it ahead of the
	// modifierNode as the new upstream node so that the modifierNode has an
	// input mesh to operate on.
	//
	MPlugArray tempPlugArray;

	if( fHasHistory )
	{
		// Since we have history, look for what connections exist on the
		// meshNode "inMesh" plug. "inMesh" plugs should only ever have one
		// connection.
		//
		data.meshNodeDestPlug.connectedTo( tempPlugArray, true, false);

		// ASSERT: Only one connection should exist on meshNodeShape.inMesh!
		//
		MStatusAssert( (tempPlugArray.length() == 1),
					   "tempPlugArray.length() == 1 -- 0 or >1 connections on meshNodeShape.inMesh" );
		data.upstreamNodeSrcPlug = tempPlugArray[0];

		// Construction history only deals with shapes, so we can grab the
		// upstreamNodeShape off of the source plug.
		//
		data.upstreamNodeShape = data.upstreamNodeSrcPlug.node();
		depNodeFn.setObject( data.upstreamNodeShape );
		data.upstreamNodeSrcAttr = data.upstreamNodeSrcPlug.attribute();

		// Disconnect the upstream node and the selected node, so we can
		// replace them with our own connections below.
		//
		fDGModifier.disconnect( data.upstreamNodeSrcPlug,
								data.meshNodeDestPlug );

	}
	else	// No History (!fHasHistory)
	{
		// Use the DAG node function set to duplicate the shape of the meshNode.
		// The duplicate method will return an MObject handle to the transform
		// of the duplicated shape, so traverse the dag to locate the shape. Store
		// this duplicate shape as our "upstream" node to drive the input for the 
		// modifierNode.
		//
		dagNodeFn.setObject( data.meshNodeShape );
		data.upstreamNodeTransform = dagNodeFn.duplicate( false, false );
		dagNodeFn.setObject( data.upstreamNodeTransform );

		// Ensure that our upstreamNode is pointing to a shape.
		//
		MStatusAssert( (0 < dagNodeFn.childCount()),
					   "0 < dagNodeFn.childCount() -- Duplicate meshNode transform has no shape." );
		data.upstreamNodeShape = dagNodeFn.child(0);

		// Re-parent the upstreamNodeShape under our original transform
		//
		status = fDagModifier.reparentNode( data.upstreamNodeShape, data.meshNodeTransform );
		MCheckStatus( status, "reparentNode" );

		// Perform the DAG re-parenting
		// 
		// Note: This reparent must be performed before the deleteNode() is called.
		//		 See polyModifierCmd.h (see definition of fDagModifier) for more details.
		//
		status = fDagModifier.doIt();
		MCheckStatus( status, "fDagModifier.doIt()" );

		// Mark the upstreamNodeShape (the original shape) as an intermediate object
		// (making it invisible to the user)
		//
		dagNodeFn.setObject( data.upstreamNodeShape );
		dagNodeFn.setIntermediateObject( true );

		// Get the upstream node source attribute
		//
		data.upstreamNodeSrcAttr = dagNodeFn.attribute( "outMesh" );
		data.upstreamNodeSrcPlug = dagNodeFn.findPlug( "outMesh", &status );

		// Remove the duplicated transform node (clean up)
		//
		status = fDagModifier.deleteNode( data.upstreamNodeTransform );
		MCheckStatus( status, "deleteNode" );

		// Perform the DAG delete node
		//
		// Note: This deleteNode must be performed after the reparentNode() method is
		//		 completed. See polyModifierCmd.h (see definition of fDagModifier) for
		//		 details.
		//
		status = fDagModifier.doIt();
		MCheckStatus( status, "fDagModifier.doIt()" );

		// Cache the DAG path to the duplicate shape
		//
		dagNodeFn.getPath( fDuplicateDagPath );
	}

	return status;
}

MStatus polyModifierCmd::processModifierNode( MObject modifierNode,
											  modifyPolyData& data )
{
	MStatus status = MS::kSuccess;

	MFnDependencyNode depNodeFn ( modifierNode );
	data.modifierNodeSrcAttr = depNodeFn.attribute( "outMesh" );
	data.modifierNodeDestAttr = depNodeFn.attribute( "inMesh" );

	return status;
}

MStatus polyModifierCmd::processTweaks( modifyPolyData& data )
{
	MStatus status = MS::kSuccess;

	// Clear tweak undo information (to be rebuilt)
	//
	fTweakIndexArray.clear();
	fTweakVectorArray.clear();

	// Extract the tweaks and place them into a polyTweak node. This polyTweak node
	// will be placed ahead of the modifier node to maintain the order of operations.
	// Special care must be taken into recreating the tweaks:
	//
	//		1) Copy tweak info (including connections!)
	//		2) Remove tweak info from both meshNode and a duplicate meshNode (if applicable)
	//		3) Cache tweak info for undo operations
	//
	if( fHasTweaks )
	{
		// Declare our function sets
		//
		MFnDependencyNode depNodeFn;

		// Declare our attributes and plugs
		//
		MPlug	meshTweakPlug;
		MPlug	upstreamTweakPlug;
		MObject tweakNodeTweakAttr;

		// Declare our tweak processing variables
		//
		MPlug				tweak;
		MPlug				tweakChild;
		MObject				tweakData;
		MObjectArray		tweakDataArray;
		MFloatVector		tweakVector;

		MIntArray			tweakSrcConnectionCountArray;
		MPlugArray			tweakSrcConnectionPlugArray;
		MIntArray			tweakDstConnectionCountArray;
		MPlugArray			tweakDstConnectionPlugArray;

		MPlugArray			tempPlugArray;

		unsigned i;
		unsigned j;
		unsigned k;

		// Create the tweak node and get its attributes
		//
		data.tweakNode = fDGModifier.createNode( "polyTweak" );
		depNodeFn.setObject( data.tweakNode );
		data.tweakNodeSrcAttr = depNodeFn.attribute( "output" );
		data.tweakNodeDestAttr = depNodeFn.attribute( "inputPolymesh" );
		tweakNodeTweakAttr = depNodeFn.attribute( "tweak" );

		depNodeFn.setObject( data.meshNodeShape );
		meshTweakPlug = depNodeFn.findPlug( "pnts" );

		// ASSERT: meshTweakPlug should be an array plug!
		//
		MStatusAssert( (meshTweakPlug.isArray()),
					   "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" );
		unsigned numElements = meshTweakPlug.numElements();

		// Gather meshTweakPlug data
		//
		for( i = 0; i < numElements; i++ )
		{
			// MPlug::numElements() only returns the number of physical elements
			// in the array plug. Thus we must use elementByPhysical index when using
			// the index i.
			//
			tweak = meshTweakPlug.elementByPhysicalIndex(i);

			// If the method fails, the element is NULL. Only append the index
			// if it is a valid plug.
			//
			if( !tweak.isNull() )
			{
				// Cache the logical index of this element plug
				//
				unsigned logicalIndex = tweak.logicalIndex();

				// Collect tweak data and cache the indices and float vectors
				//
				tweak.getValue( tweakData );
				tweakDataArray.append( tweakData );
				getFloat3PlugValue( tweak, tweakVector );
				fTweakIndexArray.append( logicalIndex );
				fTweakVectorArray.append( tweakVector );

				// Collect tweak connection data
				//
				// Parse down to the deepest level of the plug tree and check
				// for connections - look at the child nodes of the element plugs.
				// If any connections are found, record the connection and disconnect
				// it.
				//

				// ASSERT: The element plug should be compound!
				//
				MStatusAssert( (tweak.isCompound()),
							   "tweak.isCompound() -- Element tweak plug is not compound" );

				unsigned numChildren = tweak.numChildren();
				for( j = 0; j < numChildren; j++ )
				{
					tweakChild = tweak.child(j);
					if( tweakChild.isConnected() )
					{
						// Get all connections with this plug as source, if they exist
						//
						tempPlugArray.clear();
						if( tweakChild.connectedTo( tempPlugArray, false, true ) )
						{
							unsigned numSrcConnections = tempPlugArray.length();
							tweakSrcConnectionCountArray.append( numSrcConnections );

							for( k = 0; k < numSrcConnections; k++ )
							{
								tweakSrcConnectionPlugArray.append( tempPlugArray[k] );
								fDGModifier.disconnect( tweakChild, tempPlugArray[k] );
							}
						}
						else
						{
							tweakSrcConnectionCountArray.append(0);
						}

						// Get the connection with this plug as destination, if it exists
						//
						tempPlugArray.clear();
						if( tweakChild.connectedTo( tempPlugArray, true, false ) )
						{
							// ASSERT: tweakChild should only have one connection as destination!
							//
							MStatusAssert( (tempPlugArray.length() == 1),
										   "tempPlugArray.length() == 1 -- 0 or >1 connections on tweakChild" );

							tweakDstConnectionCountArray.append(1);
							tweakDstConnectionPlugArray.append( tempPlugArray[0] );
							fDGModifier.disconnect( tempPlugArray[0], tweakChild );
						}
						else
						{
							tweakDstConnectionCountArray.append(0);
						}
					}
					else
					{
						tweakSrcConnectionCountArray.append(0);
						tweakDstConnectionCountArray.append(0);
					}
				}
			}
		}

		// Apply meshTweakPlug data to our polyTweak node
		//
		MPlug polyTweakPlug( data.tweakNode, tweakNodeTweakAttr );
		unsigned numTweaks = fTweakIndexArray.length();
		int srcOffset = 0;
		int dstOffset = 0;

		for( i = 0; i < numTweaks; i++ )
		{
			// Apply tweak data
			//
			tweak = polyTweakPlug.elementByLogicalIndex( fTweakIndexArray[i] );
			tweak.setValue( tweakDataArray[i] );

			// ASSERT: Element plug should be compound!
			//
			MStatusAssert( (tweak.isCompound()),
						   "tweak.isCompound() -- Element plug, 'tweak', is not compound" );

			unsigned numChildren = tweak.numChildren();
			for( j = 0; j < numChildren; j++ )
			{
				tweakChild = tweak.child(j);

				// Apply tweak source connection data
				//
				if( 0 < tweakSrcConnectionCountArray[i*numChildren + j] )
				{
					for( k = 0;
						 k < (unsigned) tweakSrcConnectionCountArray[i*numChildren + j];
						 k++ )
					{
						fDGModifier.connect( tweakChild,
											 tweakSrcConnectionPlugArray[srcOffset] );
						srcOffset++;
					}
				}
						
				// Apply tweak destination connection data
				//
				if( 0 < tweakDstConnectionCountArray[i*numChildren + j] )
				{
					fDGModifier.connect( tweakDstConnectionPlugArray[dstOffset],
										 tweakChild );
					dstOffset++;
				}
			}
		}

/*		// Now, set the tweak values on the meshNode(s) to zero (History dependent)
		//
		MFnNumericData numDataFn;
		MObject nullVector;

		// Create a NULL vector (0,0,0) using MFnNumericData to pass into the plug
		//
		numDataFn.create( MFnNumericData::k3Float );
		numDataFn.setData( 0, 0, 0 );
		nullVector = numDataFn.object();

		for( i = 0; i < numTweaks; i++ )
		{
			// Access using logical indices since they are the only plugs guaranteed
			// to hold tweak data.
			//
			tweak = meshTweakPlug.elementByLogicalIndex( fTweakIndexArray[i] );
			tweak.setValue( nullVector );
		}

		// Only have to clear the tweaks off the duplicate mesh if we do not have history
		// and we want history.
		//
		if( !fHasHistory && fHasRecordHistory )
		{
			depNodeFn.setObject( data.upstreamNodeShape );
			upstreamTweakPlug = depNodeFn.findPlug( "pnts" );

			if( !upstreamTweakPlug.isNull() )
			{
				for( i = 0; i < numTweaks; i++ )
				{
					tweak = meshTweakPlug.elementByLogicalIndex( fTweakIndexArray[i] );
					tweak.setValue( nullVector );
				}
			}
		}
*/	}

	return status;
}

MStatus polyModifierCmd::connectNodes( MObject modifierNode )
//
//	Description:
//
//		This method connects up the modifier nodes, while accounting for DG factors
//		such as construction history and tweaks. The method has a series of steps which
//		it runs through to process nodes under varying circumstances:
//
//		1) Gather meshNode connection data (ie. attributes and plugs)
//
//		2) Gather upstreamNode data - This is history-dependent. If the node has history,
//									  an actual upstreamNode exists and that is used to
//									  drive the input of our modifierNode.
//
//									  Otherwise, if the node does not have history, the
//									  meshNode is duplicated, set as an intermediate object
//									  and regarded as our new upstreamNode which will drive
//									  the input of our modifierNode. The case with history
//									  already has this duplicate meshNode at the top, driving
//									  all other history nodes and serving as a reference
//									  to the "original state" of the node before any
//									  modifications.
//
//		3) Gather modifierNode connection data
//
//		4) Process tweak data (if it exists) - This is history-dependent. If there is
//											   history, the tweak data is extracted and deleted
//											   from the meshNode and encapsulated inside a
//											   polyTweak node. The polyTweak node is then
//											   inserted ahead of the modifier node.
//
//											   If there is no history, the same is done as
//											   in the history case, except the tweaks are
//											   deleted from the duplicate meshNode in addition
//											   to the actual meshNode.
//
//		5) Connect the nodes
//
//		6) Collapse/Bake nodes into the actual meshNode if the meshNode had no previous
//		   construction history and construction history recording is turned off.
//		   (ie. (!fHasHistory && !fHasRecordHistory) == true )
//
{
	MStatus status;

	// Declare our internal processing data structure (see polyModifierCmd.h for definition)
	//
	modifyPolyData data;

	// Get the mesh node, plugs and attributes
	//
	status = processMeshNode( data );
	MCheckStatus( status, "processMeshNode" );

	// Get upstream node, plugs and attributes
	//
	status = processUpstreamNode( data );
	MCheckStatus( status, "processUpstreamNode" );

	// Get the modifierNode attributes
	//
	status = processModifierNode( modifierNode,
						 data );
	MCheckStatus( status, "processModifierNode" );

	// Process tweaks on the meshNode
	//
	status = processTweaks( data );
	MCheckStatus( status, "processTweaks" );

	// Connect the nodes
	//
	if( fHasTweaks )
	{
		MPlug tweakDestPlug( data.tweakNode, data.tweakNodeDestAttr );
		status = fDGModifier.connect( data.upstreamNodeSrcPlug, tweakDestPlug );
		MCheckStatus( status, "upstream-tweak connect failed" );

		MPlug tweakSrcPlug( data.tweakNode, data.tweakNodeSrcAttr );
		MPlug modifierDestPlug( modifierNode, data.modifierNodeDestAttr );
		status = fDGModifier.connect( tweakSrcPlug, modifierDestPlug );
		MCheckStatus( status, "tweak-modifier connect failed" );
	}
	else
	{
		MPlug modifierDestPlug( modifierNode, data.modifierNodeDestAttr );
		status = fDGModifier.connect( data.upstreamNodeSrcPlug, modifierDestPlug );
		MCheckStatus( status, "upstream-modifier connect failed" );
	}

	MPlug modifierSrcPlug( modifierNode, data.modifierNodeSrcAttr );
	MPlug meshDestAttr( data.meshNodeShape, data.meshNodeDestAttr );
	status = fDGModifier.connect( modifierSrcPlug, meshDestAttr );
	MCheckStatus( status, "modifier-mesh connect failed" );
	
	status = fDGModifier.doIt();

	return status;
}

MStatus polyModifierCmd::cacheMeshData()
{
	MStatus status = MS::kSuccess;

	MFnDependencyNode depNodeFn;
	MFnDagNode dagNodeFn;

	MObject meshNode = fDagPath.node();
	MObject dupMeshNode;
	MPlug dupMeshNodeOutMeshPlug;

	// Duplicate the mesh
	//
	dagNodeFn.setObject( meshNode );
	dupMeshNode = dagNodeFn.duplicate();

	MDagPath dupMeshDagPath;
	MDagPath::getAPathTo( dupMeshNode, dupMeshDagPath );
	dupMeshDagPath.extendToShape();

	depNodeFn.setObject( dupMeshDagPath.node() );
	dupMeshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", &status );
	MCheckStatus( status, "Could not retrieve outMesh" );

	// Retrieve the meshData
	//
	status = dupMeshNodeOutMeshPlug.getValue( fMeshData );
	MCheckStatus( status, "Could not retrieve meshData" );

	// Delete the duplicated node
	//
	MGlobal::deleteNode( dupMeshNode );

	return status;
}

MStatus polyModifierCmd::cacheMeshTweaks()
{
	MStatus status = MS::kSuccess;

	// Clear tweak undo information (to be rebuilt)
	//
	fTweakIndexArray.clear();
	fTweakVectorArray.clear();

	// Extract the tweaks and store them in our local tweak cache members
	//
	if( fHasTweaks )
	{
		// Declare our function sets
		//
		MFnDependencyNode depNodeFn;

		MObject meshNode = fDagPath.node();
		MPlug	meshTweakPlug;

		// Declare our tweak processing variables
		//
		MPlug				tweak;
		MPlug				tweakChild;
		MObject				tweakData;
		MObjectArray		tweakDataArray;
		MFloatVector		tweakVector;

		MPlugArray			tempPlugArray;

		unsigned i;

		depNodeFn.setObject( meshNode );
		meshTweakPlug = depNodeFn.findPlug( "pnts" );

		// ASSERT: meshTweakPlug should be an array plug!
		//
		MStatusAssert( (meshTweakPlug.isArray()),
					   "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" );
		unsigned numElements = meshTweakPlug.numElements();

		// Gather meshTweakPlug data
		//
		for( i = 0; i < numElements; i++ )
		{
			// MPlug::numElements() only returns the number of physical elements
			// in the array plug. Thus we must use elementByPhysical index when using
			// the index i.
			//
			tweak = meshTweakPlug.elementByPhysicalIndex(i);

			// If the method fails, the element is NULL. Only append the index
			// if it is a valid plug.
			//
			if( !tweak.isNull() )
			{
				// Cache the logical index of this element plug
				//
				unsigned logicalIndex = tweak.logicalIndex();

				// Collect tweak data and cache the indices and float vectors
				//
				getFloat3PlugValue( tweak, tweakVector );
				fTweakIndexArray.append( logicalIndex );
				fTweakVectorArray.append( tweakVector );
			}
		}
	}

	return status;
}

MStatus polyModifierCmd::undoCachedMesh()
{
	MStatus status;

	// Only need to restore the cached mesh if there was no history. Also
	// check to make sure that we are in the record history state.
	//
	MStatusAssert( (fHasRecordHistory), "fHasRecordHistory == true" );

	if( !fHasHistory )
	{
		MFnDependencyNode depNodeFn;

		MString meshNodeName;
		MObject meshNodeShape;
		MPlug	meshNodeDestPlug;
		MPlug	meshNodeOutMeshPlug;
		MObject dupMeshNodeShape;
		MPlug	dupMeshNodeSrcPlug;

		meshNodeShape = fDagPath.node();
		dupMeshNodeShape = fDuplicateDagPath.node();

		depNodeFn.setObject( meshNodeShape );
		meshNodeName = depNodeFn.name();
		meshNodeDestPlug = depNodeFn.findPlug( "inMesh", &status );
		MCheckStatus( status, "Could not retrieve inMesh" );
		meshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", &status );
		MCheckStatus( status, "Could not retrieve outMesh" );

		depNodeFn.setObject( dupMeshNodeShape );
		dupMeshNodeSrcPlug = depNodeFn.findPlug( "outMesh", &status );
		MCheckStatus( status, "Could not retrieve outMesh" );

		// For the case with tweaks, we cannot write the mesh directly back onto
		// the cachedInMesh, since the shape can have out of date information from the
		// cachedInMesh, thus we temporarily connect the duplicate mesh shape to the
		// mesh shape and force a DG evaluation.
		//
		// For the case without tweaks, we can simply write onto the outMesh, since
		// the shape relies solely on an outMesh when there is no history nor tweaks.
		//
		if( fHasTweaks )
		{
			MDGModifier dgModifier;
			dgModifier.connect( dupMeshNodeSrcPlug, meshNodeDestPlug );
			status = dgModifier.doIt();
			MCheckStatus( status, "Could not connect dupMeshNode -> meshNode" );

			// Need to force a DG evaluation now that the input has been changed.
			//
			MString cmd( "dgeval -src " );
			cmd += meshNodeName;
			cmd += ".inMesh";
			status = MGlobal::executeCommand( cmd, false, false );
			MCheckStatus( status, "Could not force DG eval" );

			// Disconnect the duplicate meshNode now
			//
			dgModifier.undoIt();
		}
		else
		{
			MObject meshData;
			status = dupMeshNodeSrcPlug.getValue( meshData );
			MCheckStatus( status, "Could not retrieve meshData" );
			status = meshNodeOutMeshPlug.setValue( meshData );
			MCheckStatus( status, "Could not set outMesh" );
		}
	}

	return status;
}

MStatus polyModifierCmd::undoTweakProcessing()
{
	MStatus status = MS::kSuccess;

	if( fHasTweaks )
	{
		MFnDependencyNode depNodeFn;

		MObject	meshNodeShape;
		MPlug	meshTweakPlug;
		MPlug	tweak;
		MObject	tweakData;

		meshNodeShape = fDagPath.node();
		depNodeFn.setObject( meshNodeShape );
		meshTweakPlug = depNodeFn.findPlug( "pnts" );

		MStatusAssert( (meshTweakPlug.isArray()),
					   "meshTweakPlug.isArray() -- meshTweakPlug is not an array plug" );

		unsigned i;
		unsigned numElements = fTweakIndexArray.length();

		for( i = 0; i < numElements; i++ )
		{
			tweak = meshTweakPlug.elementByLogicalIndex( fTweakIndexArray[i] );
			getFloat3asMObject( fTweakVectorArray[i], tweakData );
			tweak.setValue( tweakData );
		}

		// In the case of no history, the duplicate node shape will be disconnected on undo
		// so, there is no need to undo the tweak processing on it.
		//
	}

	return status;
}

MStatus polyModifierCmd::undoDirectModifier()
{
	MStatus status;

	MFnDependencyNode depNodeFn;
	MFnDagNode dagNodeFn;

	MObject meshNode = fDagPath.node();
	depNodeFn.setObject( meshNode );
	
	// For the case with tweaks, we cannot write the mesh directly back onto
	// the cachedInMesh, since the shape can have out of date information from the
	// cachedInMesh. Thus we temporarily create an duplicate mesh, place our
	// old mesh on the outMesh attribute of our duplicate mesh, connect the
	// duplicate mesh shape to the mesh shape, and force a DG evaluation.
	//
	// For the case without tweaks, we can simply write onto the outMesh, since
	// the shape relies solely on an outMesh when there is no history nor tweaks.
	//
	if( fHasTweaks )
	{
		// Retrieve the inMesh and name of our mesh node (for the DG eval)
		//
		depNodeFn.setObject( meshNode );
		MPlug meshNodeInMeshPlug = depNodeFn.findPlug( "inMesh", &status );
		MCheckStatus( status, "Could not retrieve inMesh" );
		MString meshNodeName = depNodeFn.name();

		// Duplicate our current mesh
		//
		dagNodeFn.setObject( meshNode );
		MObject dupMeshNode = dagNodeFn.duplicate();

		// The dagNodeFn::duplicate() returns a transform, but we need a shape
		// so retrieve the DAG path and extend it to the shape.
		//
		MDagPath dupMeshDagPath;
		MDagPath::getAPathTo( dupMeshNode, dupMeshDagPath );
		dupMeshDagPath.extendToShape();

		// Retrieve the outMesh of the duplicate mesh and set our mesh data back
		// on it.
		//
		depNodeFn.setObject( dupMeshDagPath.node() );
		MPlug dupMeshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", &status );
		MCheckStatus( status, "Could not retrieve outMesh" );
		status = dupMeshNodeOutMeshPlug.setValue( fMeshData );

		// Temporarily connect the duplicate mesh node to our mesh node
		//
		MDGModifier dgModifier;
		dgModifier.connect( dupMeshNodeOutMeshPlug, meshNodeInMeshPlug );
		status = dgModifier.doIt();
		MCheckStatus( status, "Could not connect dupMeshNode -> meshNode" );

		// Need to force a DG evaluation now that the input has been changed.
		//
		MString cmd("dgeval -src ");
		cmd += meshNodeName;
		cmd += ".inMesh";
		status = MGlobal::executeCommand( cmd, false, false );
		MCheckStatus( status, "Could not force DG eval" );

		// Disconnect and delete the duplicate mesh node now
		//
		dgModifier.undoIt();
		MGlobal::deleteNode( dupMeshNode );

		// Restore the tweaks on the mesh
		//
		status = undoTweakProcessing();
	}
	else
	{
		// Restore the original mesh by writing the old mesh data (fMeshData) back
		// onto the outMesh of our meshNode
		//
		depNodeFn.setObject( meshNode );
		MPlug meshNodeOutMeshPlug = depNodeFn.findPlug( "outMesh", &status );
		MCheckStatus( status, "Could not retrieve outMesh" );
		status = meshNodeOutMeshPlug.setValue( fMeshData );
		MCheckStatus( status, "Could not set meshData" );
	}

	return status;
}

MStatus polyModifierCmd::getFloat3PlugValue( MPlug plug, MFloatVector & value )
{
	// Retrieve the value as an MObject
	//
	MObject object;
	plug.getValue( object );

	// Convert the MObject to a float3
	//
	MFnNumericData numDataFn( object );
	numDataFn.getData( value[0], value[1], value[2] );
	return MS::kSuccess;
}

MStatus polyModifierCmd::getFloat3asMObject( MFloatVector value, MObject& object )
{
	// Convert the float value into an MObject
	//
	MFnNumericData numDataFn;
	numDataFn.create( MFnNumericData::k3Float );
	numDataFn.setData( value[0], value[1], value[2] );
	object = numDataFn.object();
	return MS::kSuccess;
}
