//-
// ==========================================================================
// 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 <maya/MFnPlugin.h>
#include <maya/MPxNode.h>
#include <maya/MString.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h> 
#include <maya/MFnNumericAttribute.h>
#include <maya/MFloatVector.h>


// Error macro: if not successful, print error message and return
// the MStatus instance containing the error code.
// Assumes that "stat" contains the error value
#define PERRORfail(stat,msg) \
    if (!(stat)) { stat.perror((msg)); return (stat); }


//////////////////////////
// Forward Declarations //
//////////////////////////

class shiftNode;

/////////////////////////////////
// Plugin Lambert Shader Class //
/////////////////////////////////

class shiftNode : public MPxNode
{
	public: // Methods
	shiftNode();
	virtual ~shiftNode();
	
	virtual MStatus compute( const MPlug&, MDataBlock& );
	
	static  void *          creator();
	static  MStatus         initialize();

	//  Id tag for use with binary file format
	static MTypeId id;

	private:

	float ox, oy, oz;
	int initID, doneID;
	static bool firstTime;

	// Input
	static MObject aShift;
	static MObject aDist;

	static MObject aColor;

	static MObject aRefPointCamera;
	static MObject aUv;

	// Output
	static MObject aOutColor;

};


// attribute information

MTypeId shiftNode::id( 0x8001a );

// input

MObject     shiftNode::aShift;
MObject     shiftNode::aDist;
MObject     shiftNode::aColor;
MObject     shiftNode::aRefPointCamera;
MObject     shiftNode::aUv;

// output

MObject     shiftNode::aOutColor;

shiftNode::shiftNode()
{
}

shiftNode::~shiftNode()
{
}

// creates an instance of the node
void * shiftNode::creator()
{
	return new shiftNode();
}

// initializes attribute information
MStatus shiftNode::initialize()
{
	MStatus stat;

	MFnNumericAttribute nAttr; 

	// shift

	aShift = nAttr.create( "shift", "sh", 
						  MFnNumericData::kBoolean, false );
	stat = addAttribute( aShift );
	PERRORfail( stat, "addAttribute shift" );

	// distance

	aDist = nAttr.create( "distance", "dis", 
						 MFnNumericData::kFloat, 0.0 );
	stat = addAttribute( aDist );
	PERRORfail( stat, "addAttribute dist" );

	// inColor

	aColor = nAttr.createColor( "color", "c" );
	nAttr.setStorable(false);
	stat = addAttribute( aColor );
	PERRORfail( stat, "addAttribute inColor" );
	
	// refPointCamera

	aRefPointCamera = nAttr.createPoint( "refPointCamera", "rpc" );
	nAttr.setStorable(false);
	nAttr.setWritable(true);
	nAttr.setRenderSource(true);
	nAttr.setHidden(true);
	stat = addAttribute( aRefPointCamera );
	PERRORfail( stat, "addAttribute refPointCamera" );

	// uv

	MObject u = nAttr.create( "uCoord", "u", MFnNumericData::kFloat, 0.0 );
	MObject v = nAttr.create( "vCoord", "v", MFnNumericData::kFloat, 0.0 );
	aUv = nAttr.create( "uvCoord", "uv", u, v );
	nAttr.setStorable(false);
	nAttr.setWritable(true);
	nAttr.setRenderSource(true);
	nAttr.setHidden(true);
	stat = addAttribute( aUv );
	PERRORfail( stat, "addAttribute uv" );

	// create output attributes here

    aOutColor = nAttr.createColor( "outColor", "oc" );
	PERRORfail(stat, "initialize create outColor attribute");
	nAttr.setReadable( true );
	nAttr.setWritable( false );
	stat = addAttribute( aOutColor );
	PERRORfail(stat, "addAttribute(outColor)");
	
	// attribute affects

	attributeAffects ( aShift, aOutColor );
	attributeAffects ( aDist, aOutColor );
	attributeAffects ( aColor, aOutColor );
	attributeAffects ( aRefPointCamera, aOutColor );
	attributeAffects ( aUv, aOutColor );

	// we need to do the followings to cause other input attributes
	// to evaluate with the new values we set

	attributeAffects ( aUv, aUv );
	attributeAffects ( aRefPointCamera, aRefPointCamera );

	return MS::kSuccess;
}


/*

This function gets called by Maya to evaluate the texture.

*/
MStatus shiftNode::compute( const MPlug& plug, MDataBlock& data ) 
{
	MStatus stat;

	if ((plug != aOutColor) && (plug.parent() != aOutColor))
		return MS::kUnknownParameter;

	MDataHandle colorH;
	MFloatVector color;

	MDataHandle shiftH = data.inputValue( aShift, &stat);
	PERRORfail(stat, "compute getting shift attr");
	bool shiftIt = shiftH.asBool();

	MDataHandle distH = data.inputValue( aDist, &stat);
	PERRORfail(stat, "compute getting distance attr");
	float distance = distH.asFloat();

	MFloatVector clr;

	if ( shiftIt && distance != 0.0 )
	{
		// first evaluate color at default sample posiiton

		clr = data.inputValue( aColor ).asFloatVector();

		// uv is used by 2d textures
		// refPointCamera is used by 3d textures

		MDataHandle refPointCamH = data.inputValue( aRefPointCamera, &stat);
		PERRORfail(stat, "compute getting refPointCamera attr");
		MFloatVector refPC = refPointCamH.asFloatVector();

		// get current UV

		const float2 & oldUV = data.inputValue(aUv).asFloat2();

		// shift and set the uv/refPointCamera values so
		// we can sample around the current uv/refPointCamera

		MDataHandle outUV = data.outputValue( aUv );
		MDataHandle outPC = data.outputValue( aRefPointCamera );

		outUV.set( oldUV[0]-distance, oldUV[1] );
		outPC.set( refPC.x + distance, refPC.y + distance, refPC.z + distance);
		colorH = data.inputValue( aColor, &stat);	// evaluate at new pos
		color = colorH.asFloatVector();
		clr += color;

		outUV.set( oldUV[0]+distance, oldUV[1] );
		outPC.set( refPC.x - distance, refPC.y + distance, refPC.z + distance);
		colorH = data.inputValue( aColor, &stat);	// evaluate at new pos
		color = colorH.asFloatVector();
		clr += color;

		outUV.set( oldUV[0], oldUV[1]-distance );
		outPC.set( refPC.x + distance, refPC.y - distance, refPC.z + distance);
		colorH = data.inputValue( aColor, &stat);	// evaluate at new pos
		color = colorH.asFloatVector();
		clr += color;

		outUV.set( oldUV[0], oldUV[1]+distance );
		outPC.set( refPC.x - distance, refPC.y - distance, refPC.z + distance);
		colorH = data.inputValue( aColor, &stat);	// evaluate at new pos
		color = colorH.asFloatVector();
		clr += color;

		clr /= 5.0;	// average the colors from all locations

		// set sample data back to original values

		outUV.set( oldUV[0], oldUV[1] );
		outPC.set( refPC.x, refPC.y, refPC.z ); 
	}
	else
	{
		colorH = data.inputValue( aColor, &stat);
		clr = colorH.asFloatVector();
	}

	MDataHandle outColorHandle = data.outputValue( aOutColor );
	MFloatVector& oclr = outColorHandle.asFloatVector();
	oclr = clr;
	outColorHandle.setClean();

	return MS::kSuccess;
}


//
//
//

MStatus initializePlugin( MObject obj )
{
   const MString UserClassify( "utility/general" );

	MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.5", "Any");
	plugin.registerNode( "shiftNode", shiftNode::id,
                         shiftNode::creator, shiftNode::initialize,
                         MPxNode::kDependNode, &UserClassify );

   return MS::kSuccess;

}

MStatus uninitializePlugin( MObject obj )
{
	MFnPlugin plugin( obj );
	plugin.deregisterNode( shiftNode::id );

	return MS::kSuccess;
}
