//-
// ==========================================================================
// 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.
// ==========================================================================
//+

#include <geometrySurfaceConstraint.h>

//
//	Example: geometrySurfaceConstraint
//	This example demonstrates how to use the MPxConstraint
//	and MPxConstraintCommand classes to create a 
//	geometry constraint.  This type of constraint will
//	keep the constrained object attached to the target 
//	as the target is moved.
//	The constrained object can be constrained to one of
//	mutliple targets.  You can choose to constrain to
//	the target of the highest or lowest weight.
//

/*
loadPlugin geometrySurfaceConstraint;

// 1. cylinder constrained to plane
file -f -new;
polyPlane -w 1 -h 1 -sx 10 -sy 10 -ax 0 1 0 -cuv 2 -ch 1;
scale -r 15 15 15;
polyCylinder -r 1 -h 2 -sx 20 -sy 1 -sz 1 -ax 0 1 0 -rcp 0 -cuv 3 -ch 1;
select -cl;
select -r pPlane1 pCylinder1;
geometrySurfaceConstraint -weight 1;

// 2. cylinder constrained to one of two planes
// depending on plane weight
file -f -new;
polyPlane -w 1 -h 1 -sx 10 -sy 10 -ax 0 1 0 -cuv 2 -ch 1;
scale -r 10 10 10;
polyPlane -w 1 -h 1 -sx 10 -sy 10 -ax 0 1 0 -cuv 2 -ch 1;
scale -r 15 15 15;
polyCylinder -r 1 -h 2 -sx 20 -sy 1 -sz 1 -ax 0 1 0 -rcp 0 -cuv 3 -ch 1;
select -cl;
select -r pPlane1 pPlane2 pCylinder1;
geometryConstraint -weight 1.0;
// change plane weight to move constrained object
geometryConstraint -e -w 10.0 pPlane2 pCylinder1;
*/

//
//	Node implementation
//

MTypeId     geometrySurfaceConstraint::id( 0x8103F );
MObject     geometrySurfaceConstraint::compoundTarget;        
MObject     geometrySurfaceConstraint::targetGeometry;       
MObject     geometrySurfaceConstraint::targetWeight;       
MObject     geometrySurfaceConstraint::constraintParentInverseMatrix;       
MObject     geometrySurfaceConstraint::constraintGeometry;       


geometrySurfaceConstraint::geometrySurfaceConstraint() 
{
	weightType = geometrySurfaceConstraintCommand::kLargestWeight;
}

geometrySurfaceConstraint::~geometrySurfaceConstraint() 
{
}

void geometrySurfaceConstraint::postConstructor()
{
}

MStatus geometrySurfaceConstraint::compute( const MPlug& plug, MDataBlock& block )
{	
	MStatus returnStatus;
 
	if ( plug == geometrySurfaceConstraint::constraintGeometry )
	{
		//
		block.inputValue(constraintParentInverseMatrix);
		//
		MArrayDataHandle targetArray = block.inputArrayValue( compoundTarget );
		unsigned int targetArrayCount = targetArray.elementCount();
		double weight,selectedWeight = 0;
		if ( weightType == geometrySurfaceConstraintCommand::kSmallestWeight )
			selectedWeight = FLT_MAX;
		MObject selectedMesh;
		unsigned int i;
		for ( i = 0; i < targetArrayCount; i++ )
		{
			MDataHandle targetElement = targetArray.inputValue();
			weight = targetElement.child(targetWeight).asDouble();
			if ( !equivalent(weight,0.0))
			{
				if ( weightType == geometrySurfaceConstraintCommand::kLargestWeight )
				{
					if ( weight > selectedWeight )
					{
						MObject mesh = targetElement.child(targetGeometry).asMesh();
						if ( !mesh.isNull() )
						{
							selectedMesh = mesh;
							selectedWeight =  weight;
						}
					}
				}
				else
				{
					if  ( weight < selectedWeight )
					{
						MObject mesh = targetElement.child(targetGeometry).asMesh();
						if ( !mesh.isNull() )
						{
							selectedMesh = mesh;
							selectedWeight =  weight;
						}
					}
				}
			}
			targetArray.next();
		}
		//
		if ( selectedMesh.isNull() )
		{
			block.setClean(plug);
		}
		else
		{
			// The transform node via the geometry attribute will take care of
			// updating the location of the constrained geometry.
			MDataHandle outputConstraintGeometryHandle = block.outputValue(constraintGeometry);
			outputConstraintGeometryHandle.setMObject(selectedMesh);
		}
	} 
	else 
	{
		return MS::kUnknownParameter;
	}

	return MS::kSuccess;
}

const MObject geometrySurfaceConstraint::weightAttribute() const
{
	return geometrySurfaceConstraint::targetWeight;
}

const MObject geometrySurfaceConstraint::targetAttribute() const
{
	return geometrySurfaceConstraint::compoundTarget;
}

void geometrySurfaceConstraint::getOutputAttributes(MObjectArray& attributeArray)
{
	attributeArray.clear();
	attributeArray.append( geometrySurfaceConstraint::constraintGeometry );
}

void* geometrySurfaceConstraint::creator()
{
	return new geometrySurfaceConstraint();
}

MStatus geometrySurfaceConstraint::initialize()
{
	MFnNumericAttribute nAttr;
	MStatus				status;

	// constraint attributes

	{	// Geometry: mesh, readable, not writable, delete on disconnect
		MFnTypedAttribute typedAttrNotWritable;
		geometrySurfaceConstraint::constraintGeometry =
			typedAttrNotWritable.create( "constraintGeometry", "cg", MFnData::kMesh, &status ); 	
		if (!status) { status.perror("typedAttrNotWritable.create:cgeom"); return status;}
		status = typedAttrNotWritable.setReadable(true);
		if (!status) { status.perror("typedAttrNotWritable.setReadable:cgeom"); return status;}
		status = typedAttrNotWritable.setWritable(false);
		if (!status) { status.perror("typedAttrNotWritable.setWritable:cgeom"); return status;}
		status = typedAttrNotWritable.setDisconnectBehavior(MFnAttribute::kDelete);
		if (!status) { status.perror("typedAttrNotWritable.setDisconnectBehavior:cgeom"); return status;}
	}
	{	// Parent inverse matrix: delete on disconnect
		MFnTypedAttribute typedAttr;
		geometrySurfaceConstraint::constraintParentInverseMatrix =
			typedAttr.create( "constraintPim", "ci", MFnData::kMatrix, &status ); 	
		if (!status) { status.perror("typedAttr.create:matrix"); return status;}
		status = typedAttr.setDisconnectBehavior(MFnAttribute::kDelete);
		if (!status) { status.perror("typedAttr.setDisconnectBehavior:cgeom"); return status;}

		// Target geometry: mesh, delete on disconnect
		geometrySurfaceConstraint::targetGeometry =
			typedAttr.create( "targetGeometry", "tg", MFnData::kMesh, &status ); 	
		if (!status) { status.perror("typedAttr.create:tgeom"); return status;}
		status = typedAttr.setDisconnectBehavior(MFnAttribute::kDelete);
		if (!status) { status.perror("typedAttr.setDisconnectBehavior:cgeom"); return status;}
	}
	{	// Target weight: double, min 0, default 1.0, keyable, delete on disconnect
		MFnNumericAttribute typedAttrKeyable;
		geometrySurfaceConstraint::targetWeight 
			= typedAttrKeyable.create( "weight", "wt", MFnNumericData::kDouble, 1.0, &status );
		if (!status) { status.perror("typedAttrKeyable.create:weight"); return status;}
		status = typedAttrKeyable.setMin( (double) 0 );
		if (!status) { status.perror("typedAttrKeyable.setMin"); return status;}
		status = typedAttrKeyable.setKeyable( true );
		if (!status) { status.perror("typedAttrKeyable.setKeyable"); return status;}
		status = typedAttrKeyable.setDisconnectBehavior(MFnAttribute::kDelete);
		if (!status) { status.perror("typedAttrKeyable.setDisconnectBehavior:cgeom"); return status;}
	}
	{	// Compound target(geometry,weight): array, delete on disconnect
		MFnCompoundAttribute compoundAttr;
		geometrySurfaceConstraint::compoundTarget = 
			compoundAttr.create( "target", "tgt",&status );
		if (!status) { status.perror("compoundAttr.create"); return status;}
		status = compoundAttr.addChild( geometrySurfaceConstraint::targetGeometry );
		if (!status) { status.perror("compoundAttr.addChild"); return status;}
		status = compoundAttr.addChild( geometrySurfaceConstraint::targetWeight );
		if (!status) { status.perror("compoundAttr.addChild"); return status;}
		status = compoundAttr.setArray( true );
		if (!status) { status.perror("compoundAttr.setArray"); return status;}
		status = compoundAttr.setDisconnectBehavior(MFnAttribute::kDelete);
		if (!status) { status.perror("typedAttrKeyable.setDisconnectBehavior:cgeom"); return status;}
	}

	status = addAttribute( geometrySurfaceConstraint::constraintParentInverseMatrix );
	if (!status) { status.perror("addAttribute"); return status;}
	status = addAttribute( geometrySurfaceConstraint::constraintGeometry );
	if (!status) { status.perror("addAttribute"); return status;}
	status = addAttribute( geometrySurfaceConstraint::compoundTarget );
	if (!status) { status.perror("addAttribute"); return status;}

	status = attributeAffects( compoundTarget, constraintGeometry );
	if (!status) { status.perror("attributeAffects"); return status;}
	status = attributeAffects( targetGeometry, constraintGeometry );
	if (!status) { status.perror("attributeAffects"); return status;}
	status = attributeAffects( targetWeight, constraintGeometry );
	if (!status) { status.perror("attributeAffects"); return status;}
	status = attributeAffects( constraintParentInverseMatrix, constraintGeometry );
	if (!status) { status.perror("attributeAffects"); return status;}

	return MS::kSuccess;
}

//
//	Command implementation
//

geometrySurfaceConstraintCommand::geometrySurfaceConstraintCommand() {}
geometrySurfaceConstraintCommand::~geometrySurfaceConstraintCommand() {}

void* geometrySurfaceConstraintCommand::creator()
{
	return new geometrySurfaceConstraintCommand();
}

void geometrySurfaceConstraintCommand::createdConstraint(MPxConstraint *constraint)
{
	if ( constraint )
	{
		geometrySurfaceConstraint *c = (geometrySurfaceConstraint*) constraint;
		c->weightType = weightType;
	}
	else
	{
		MGlobal::displayError("Failed to get created constraint.");
	}
}


MStatus geometrySurfaceConstraintCommand::parseArgs(const MArgList &argList)
{
	MStatus			ReturnStatus;
	MArgDatabase	argData(syntax(), argList, &ReturnStatus);

	if ( ReturnStatus.error() )
		return MS::kFailure;

	// Settings only work at creation time. Would need an
	// attribute on the node in order to push this state
	// into the node at any time.
	ConstraintType typ;
	if (argData.isFlagSet(kConstrainToLargestWeightFlag))
		typ = geometrySurfaceConstraintCommand::kLargestWeight;
	else if (argData.isFlagSet(kConstrainToSmallestWeightFlag))
		typ = geometrySurfaceConstraintCommand::kSmallestWeight;
	else
		typ = geometrySurfaceConstraintCommand::kLargestWeight;
	weightType = typ;

	// Need parent to process
	return MS::kUnknownParameter;
}

MStatus geometrySurfaceConstraintCommand::doIt(const MArgList &argList)
{
	MStatus ReturnStatus;

	if ( MS::kFailure == parseArgs(argList) )
		return MS::kFailure;

	return MS::kUnknownParameter;
}

MStatus geometrySurfaceConstraintCommand::connectTarget(void *opaqueTarget, int index)
{
	MStatus status = connectTargetAttribute( 
			opaqueTarget, index, geometrySurfaceConstraint::targetGeometry );
	if (!status) { status.perror("connectTargetGeometry"); return status;}
	return MS::kSuccess;
}

MStatus geometrySurfaceConstraintCommand::connectObjectAndConstraint( MDGModifier& modifier )
{
	MObject transform = transformObject();
	if ( transform.isNull() )
	{
		MGlobal::displayError("Failed to get transformObject()");
		return MS::kFailure;
	}

	MStatus status;
	MFnTransform transformFn( transform );
	MVector translate = transformFn.getTranslation(MSpace::kTransform,&status);
	if (!status) { status.perror(" transformFn.getTranslation"); return status;}

	MPlug translatePlug = transformFn.findPlug( "translate", &status );
	if (!status) { status.perror(" transformFn.findPlug"); return status;}

	if ( MPlug::kFreeToChange == translatePlug.isFreeToChange() )
	{
		MFnNumericData nd;
		MObject translateData = nd.create( MFnNumericData::k3Double, &status );
		status = nd.setData3Double( translate.x,translate.y,translate.z);
		if (!status) { status.perror("nd.setData3Double"); return status;}
		status = modifier.newPlugValue( translatePlug, translateData );
		if (!status) { status.perror("modifier.newPlugValue"); return status;}

		status = connectObjectAttribute( 
			MPxTransform::geometry, 
					geometrySurfaceConstraint::constraintGeometry, false );
		if (!status) { status.perror("connectObjectAttribute"); return status;}
	}

	status = connectObjectAttribute( 
		MPxTransform::parentInverseMatrix,
			geometrySurfaceConstraint::constraintParentInverseMatrix, true, true );
	if (!status) { status.perror("connectObjectAttribute"); return status;}

	return MS::kSuccess;
}

const MObject& geometrySurfaceConstraintCommand::constraintInstancedAttribute() const
{
	return geometrySurfaceConstraint::constraintParentInverseMatrix;
}

const MObject& geometrySurfaceConstraintCommand::constraintOutputAttribute() const
{
	return geometrySurfaceConstraint::constraintGeometry;
}

const MObject& geometrySurfaceConstraintCommand::constraintTargetInstancedAttribute() const
{
	return geometrySurfaceConstraint::targetGeometry;
}

const MObject& geometrySurfaceConstraintCommand::constraintTargetAttribute() const
{
	return geometrySurfaceConstraint::compoundTarget;
}

const MObject& geometrySurfaceConstraintCommand::constraintTargetWeightAttribute() const
{
	return geometrySurfaceConstraint::targetWeight;
}

const MObject& geometrySurfaceConstraintCommand::objectAttribute() const
{
	return MPxTransform::geometry;
}

MTypeId geometrySurfaceConstraintCommand::constraintTypeId() const
{
	return geometrySurfaceConstraint::id;
}

MPxConstraintCommand::TargetType geometrySurfaceConstraintCommand::targetType() const
{
	return MPxConstraintCommand::kGeometryShape;
}

MStatus geometrySurfaceConstraintCommand::appendSyntax()
{
	MStatus ReturnStatus;

	MSyntax theSyntax = syntax(&ReturnStatus);
	if (MS::kSuccess != ReturnStatus) {
		MGlobal::displayError("Could not get the parent's syntax");
		return ReturnStatus;
	}

	// Add our command flags
	theSyntax.addFlag( kConstrainToLargestWeightFlag, kConstrainToLargestWeightFlagLong );
	theSyntax.addFlag( kConstrainToSmallestWeightFlag, kConstrainToSmallestWeightFlagLong );

	return ReturnStatus;
}

//
//	Entry points
//

MStatus initializePlugin( MObject obj )
{ 
	MStatus   status;
	MFnPlugin plugin( obj, PLUGIN_COMPANY, "9.0", "Any");

	status = plugin.registerNode( "geometrySurfaceConstraint", geometrySurfaceConstraint::id, geometrySurfaceConstraint::creator,
		geometrySurfaceConstraint::initialize, MPxNode::kConstraintNode );
	if (!status) {
		status.perror("registerNode");
		return status;
	}

	status = plugin.registerConstraintCommand( "geometrySurfaceConstraint", geometrySurfaceConstraintCommand::creator );
	if (!status) {
		status.perror("registerConstraintCommand");
		return status;
	}

	return status;
}

MStatus uninitializePlugin( MObject obj)
{
	MStatus   status;
	MFnPlugin plugin( obj );

	status = plugin.deregisterNode( geometrySurfaceConstraint::id );
	if (!status) {
		status.perror("deregisterNode");
		return status;
	}

	status = plugin.deregisterConstraintCommand( "geometrySurfaceConstraint" );
	if (!status) {
		status.perror("deregisterNode");
		return status;
	}

	return status;
}
