//-
// ==========================================================================
// 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.
// ==========================================================================
//+

//
// Example custom transform:
//	This plug-in implements an example custom transform that
//	can be used to perform a rocking motion around the X axix.
//	Geometry of any rotation can be made a child of this transform
//	to demonstrate the effect.
//	The plug-in contains two pieces:
//	1. The custom transform node -- rockingTransformCheckNode
//	2. The custom transformation matrix -- rockingTransformCheckMatrix
//	These classes are used together in order to implement the
//	rocking motion.  Note that the rock attribute is stored outside
//	of the regular transform attributes.
//
// MEL usage:
/*
	// Create a rocking transform and make a rotated plane
	// its child.
	loadPlugin rockingTransformCheck;
	file -f -new;
	polyPlane -w 1 -h 1 -sx 10 -sy 10 -ax 0 1 0 -tx 1 -ch 1;
	select -r pPlane1 ;
	rotate -r -ws -15 -15 -15 ;
	createNode rockingTransformCheck;
	parent pPlane1 rockingTransformCheck1;
	setAttr rockingTransformCheck1.rockx 10;
*/
//

#include <maya/MPxTransform.h>
#include <maya/MPxTransformationMatrix.h>
#include <maya/MGlobal.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MIOStream.h>

#include "rockingTransformCheck.h"

//
// Initialize our static class variables
//
MTypeId rockingTransformCheckNode::idCheck(kRockingTransformCheckNodeID);
MTypeId rockingTransformCheckMatrix::idCheck(kRockingTransformCheckMatrixID);

//
// Implementation of our custom transformation matrix
//

//
//	Constructor for matrix
//
rockingTransformCheckMatrix::rockingTransformCheckMatrix()
{
}

//
// Creator for matrix
//
void *rockingTransformCheckMatrix::creator()
{
	return new rockingTransformCheckMatrix();
}


//
// Implementation of our custom transform
//

//
//	Constructor of the transform node
//
rockingTransformCheckNode::rockingTransformCheckNode()
:	ParentClass()
{
}

//
//	Constructor of the transform node
//
rockingTransformCheckNode::rockingTransformCheckNode(MPxTransformationMatrix *tm)
:	ParentClass(tm)
{
}

//
//	Destructor of the rocking transform
//
rockingTransformCheckNode::~rockingTransformCheckNode()
{
}

//
//	Method that returns the new transformation matrix
//
MPxTransformationMatrix *rockingTransformCheckNode::createTransformationMatrix()
{
	return new rockingTransformCheckMatrix();
}

//
//	Method that returns a new transform node
//
void *rockingTransformCheckNode::creator()
{
	return new rockingTransformCheckNode();
}

//
//	Debugging method
//
const char* rockingTransformCheckNode::className() 
{
	return "rockingTransformCheckNode";
}

//
//	This method allows the custom transform to apply its own locking
//	mechanism to rotation. Standard dependency graph attribute locking
//	happens automatically and cannot be modified by custom nodes.
//	If the plug should not be changed, then the value from the passed savedR
//	argument should be return to be used in the transformation matrix.
//
MEulerRotation rockingTransformCheckNode::applyRotationLocks(const MEulerRotation &toTest,
									const MEulerRotation &savedRotation,
									MStatus *ReturnStatus )
{
#ifdef ALLOW_DG_TO_HANDLE_LOCKS
	// Allow the DG to handle locking.
	return toTest;
#else
	//
	// Implement a simple lock by checking for an existing attribute
	// Use the following MEL to add the attribute:
	//	addAttr -ln "rotateLockPlug"
	//
	MStatus status;
	MObject object = thisMObject();
	MFnDependencyNode depNode( object );
	MObject rotateLockPlug = depNode.findPlug( "rotateLockPlug", &status );

	// If the lock does not exist that we return the updated value that has
	// been passed in
	if ( rotateLockPlug.isNull() )
		return toTest;
	
	// We have a lock.  Returned the original saved value of the
	// rotation.
	return savedRotation;
#endif
}

MEulerRotation rockingTransformCheckNode::applyRotationLimits(const MEulerRotation &unlimitedRotation,
									  MDataBlock & /*block*/,
									  MStatus *ReturnStatus )
{
#ifdef CHECK_ROTATION_LIMITS_USING_ATTRIBUTES
	//
	// A more complete plug-in would take this approach
	//
	MEulerRotation newRotation = unlimitedRotation;
	MDGContext context = block.context();

	updateMatrixAttrs(minRotLimitEnable, context);
	updateMatrixAttrs(maxRotLimitEnable, context);

	double3 &minLimit = block.inputValue(minRotLimit).asDouble3();
	double3 &maxLimit = block.inputValue(maxRotLimit).asDouble3();

	unsigned ii = 0, jj = 0;
	for (jj = MFnTransform::kRotateMinX, ii = 0; ii < 3; ++ii, ++jj) {
		if (isLimited((MFnTransform::LimitType)jj) && 
			newRotation[ii] < minLimit[ii]) {
			newRotation[ii] = minLimit[ii];
		}

		if (isLimited((MFnTransform::LimitType)(++jj)) &&
			newRotation[ii] > maxLimit[ii]) {
			newRotation[ii] = maxLimit[ii];
		}
	}

	if ( ReturnStatus )
		*ReturnStatus = MS::kSuccess;

	return newRotation;
#else
	//
	// For demonstration purposes we limit the rotation to 60
	// degrees and bypass the rotation limit attributes
	// 
	DegreeRadianConverter conv;
	double degrees = conv.radiansToDegrees( unlimitedRotation.x );
	if ( degrees < 60 )
		return unlimitedRotation;
	MEulerRotation euler;
	euler.x = conv.degreesToRadians( 60.0 );
	if ( ReturnStatus )
		*ReturnStatus = MS::kSuccess;
	return euler;
#endif
}

//
//	Calls applyRotationLocks && applyRotationLimits
//	This method verifies that the passed value can be set on the 
//	rotate plugs. In the base class, limits as well as locking are
//	checked by this method.
//
//	The compute, validateAndSetValue, and rotateTo functions
//	all use this method.
//
MStatus rockingTransformCheckNode::checkAndSetRotation(MDataBlock &block,
									const MPlug& plug,
									const MEulerRotation& newRotation, 
									MSpace::Space space )
{
	const MDGContext context = block.context();
	updateMatrixAttrs(context);

	MStatus status = MS::kSuccess;
	MEulerRotation outRotation = newRotation;
	if (context.isNormal()) {
		//	For easy reading.
		//
		MPxTransformationMatrix *xformMat = baseTransformationMatrix;

		//	Get the current translation in transform space for 
		//	clamping and locking.
		//
		MEulerRotation savedRotation = 
			xformMat->eulerRotation(MSpace::kTransform, &status);
		ReturnOnError(status);

		//	Translate to transform space, since the limit test needs the
		//	values in transform space. The locking test needs the values
		//	in the same space as the savedR value - which is transform 
		//	space as well.
		//
		status = baseTransformationMatrix->rotateTo(newRotation, space);
		ReturnOnError(status);

		outRotation = xformMat->eulerRotation(MSpace::kTransform, &status);
		ReturnOnError(status);

		//	Now that everything is in the same space, apply limits 
		//	and change the value to adhere to plug locking.
		//
		outRotation = applyRotationLimits(outRotation, block, &status);
		ReturnOnError(status);

		outRotation = applyRotationLocks(outRotation, savedRotation, &status);
		ReturnOnError(status);

		//	The value that remain is in transform space.
		//
		status = xformMat->rotateTo(outRotation, MSpace::kTransform);
		ReturnOnError(status);

		//	Get the value that was just set. It needs to be in transform
		//	space since it is used to set the datablock values at the
		//	end of this method. Getting the vaolue right before setting
		//	ensures that the transformation matrix and data block will
		//	be synchronized.
		//
		outRotation = xformMat->eulerRotation(MSpace::kTransform, &status);
		ReturnOnError(status);
	} else {
		//	Get the rotation for clamping and locking. This will get the
		//	rotate value in transform space.
		//
		double3 &s3 = block.inputValue(rotate).asDouble3();
		MEulerRotation savedRotation(s3[0], s3[1], s3[2]);

		//	Create a local transformation matrix for non-normal context
		//	calculations.
		//
		MPxTransformationMatrix *local = createTransformationMatrix();
		if (NULL == local) {
			MGlobal::displayError("rockingTransformCheck::checkAndSetRotation internal error");
			return status;
		}

		//	Fill the newly created transformation matrix.
		//
		status = computeLocalTransformation(local, block);
		if ( MS::kSuccess != status)
		{
			delete local;
			return status;
		}

		//	Translate the values to transform space. This will allow the 
		//	limit and locking tests to work properly.
		//
		status = local->rotateTo(newRotation, space);
		if ( MS::kSuccess != status)
		{
			delete local;
			return status;
		}

		outRotation = local->eulerRotation(MSpace::kTransform, &status);
		if ( MS::kSuccess != status)
		{
			delete local;
			return status;
		}

		//	Apply limits
		//
		outRotation = applyRotationLimits(outRotation, block, &status);
		if ( MS::kSuccess != status)
		{
			delete local;
			return status;
		}

		outRotation = applyRotationLocks(outRotation, savedRotation, &status);
		if ( MS::kSuccess != status)
		{
			delete local;
			return status;
		}

		status = local->rotateTo(outRotation, MSpace::kTransform);
		if ( MS::kSuccess != status)
		{
			delete local;
			return status;
		}

		//	Get the rotate value in transform space for placement in the
		//	datablock.
		//
		outRotation = local->eulerRotation(MSpace::kTransform, &status);
		if ( MS::kSuccess != status)
		{
			delete local;
			return status;
		}

		delete local;
	}

	MDataHandle handle = block.outputValue(plug, &status);
	if ( MS::kSuccess != status)
	{
		return status;
	}

	if (plug == rotate) {
		handle.set(outRotation.x, outRotation.y, outRotation.z);
	} else if (plug == rotateX) {
		handle.set(outRotation.x);
	} else if (plug == rotateY) {
		handle.set(outRotation.y);
	} else {
		handle.set(outRotation.z);
	}

	return status;
}
											
//
//	Method for returning the current rocking transformation matrix
//
rockingTransformCheckMatrix *rockingTransformCheckNode::getRockingTransformCheckMatrix()
{
	rockingTransformCheckMatrix *ltm = (rockingTransformCheckMatrix *) baseTransformationMatrix;
	return ltm;
}

