//-
// ==========================================================================
// 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.
// ==========================================================================
//+

//
//	Class:	particleAttrNode
//
//	Author:	Lonnie Li
//
//	Description:
//
//		particleAttrNode is an example node that extends the MPxParticleAttributeMapperNode
//	type. These nodes allow us to define custom nodes to map per-particle attribute data
//	to a particle shape.
//
//		In this particular example, we have defined two operations:
//
//		- compute2DTexture()
//		- computeMesh()
//
//		compute2DTexture() replicates the internal behaviour of Maya's internal 'arrayMapper'
//	node at the API level. Given an input texture node and a U/V coord per particle input,
//	this node will evaluate the texture at the given coordinates and map the result back
//	to the outColorPP or outValuePP attributes. See the method description for compute2DTexture()
//	for details on how to setup a proper attribute mapping graph.
//
//		computeMesh() is a user-defined behaviour. It is called when the 'computeNode' attribute
//	is connected to a polygonal mesh. From there, given a particle count, it will map the
//	object space vertex positions of the mesh to a user-defined 'outPositions' vector attribute.
//	This 'outPositions' attribute can then be connected to a vector typed, per-particle attribute
//	on the shape to drive. In our particular example we drive the particle shape's 'rampPosition'
//	attribute. For further details, see the method description for computeMesh() to setup
//	this example.
//

#include <particleAttrNode.h>

#include <maya/MStatus.h>
#include <maya/MObject.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MPlug.h>
#include <maya/MVector.h>
#include <maya/MDoubleArray.h>
#include <maya/MVectorArray.h>
#include <maya/MPlugArray.h>
#include <maya/MPointArray.h>

#include <maya/MFnDoubleArrayData.h>
#include <maya/MFnVectorArrayData.h>
#include <maya/MFnMesh.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnNumericAttribute.h>

#include <maya/MDynamicsUtil.h>

MTypeId particleAttrNode::id = 0x81036;
MObject particleAttrNode::outPositionPP;
MObject particleAttrNode::particleCount;

particleAttrNode::particleAttrNode()
{}

particleAttrNode::~particleAttrNode()
{}

void* particleAttrNode::creator()
{
	return new particleAttrNode();
}

MStatus particleAttrNode::initialize()
{
	MStatus status = MS::kSuccess;

	// In addition to the standard arrayMapper attributes,
	// create an additional vector attribute.
	//
	MFnTypedAttribute typedAttrFn;
	MVectorArray defaultVectArray;
	MFnVectorArrayData vectArrayDataFn;
	vectArrayDataFn.create( defaultVectArray );
	typedAttrFn.create( "outPosition",
						"opos",
						MFnData::kVectorArray,
						vectArrayDataFn.object(),
						&status );
	// Transient output attribute. Not writable nor storable.
	//
	typedAttrFn.setWritable(false);
	typedAttrFn.setStorable(false);
	outPositionPP = typedAttrFn.object();
	addAttribute( outPositionPP );

	MFnNumericAttribute numAttrFn;
	numAttrFn.create( "particleCount", "pc", MFnNumericData::kInt );
	particleCount = numAttrFn.object();
	addAttribute( particleCount );

	attributeAffects( computeNode, outPositionPP );
	attributeAffects( particleCount, outPositionPP );

	return status;
}

MStatus particleAttrNode::compute( const MPlug& plug, MDataBlock& block )
{
	MStatus status = MS::kUnknownParameter;
	if( (plug.attribute() == outColorPP) || (plug.attribute() == outValuePP) )
	{
		status = compute2DTexture( plug, block );
	}
	else if( plug.attribute() == outPositionPP )
	{
		status = computeMesh( plug, block );
	}
	return status;
}

MStatus particleAttrNode::compute2DTexture( const MPlug& plug, MDataBlock& block )
//
//	Description:
//
//		This computes an outputValue/Color based on input U and V coordinates.
//	The code contained herein replicates the compute code of the internal Maya
//	'arrayMapper' node.
//
//	How to use:
//
//		1) Create a ramp
//		2) Create a particleAttr node
//		3) Create an emitter with a particleShape
//		4) Create a per particle color attribute (rgbPP) on the particleShape via the AE.
//		5) connectAttr ramp1.outColor particleAttr1.computeNode
//		6) connectAttr particleShape1.age particleAttr1.vCoordPP
//		7) connectAttr particleAttr1.outColorPP particleShape1.rgbPP
//
{
	MStatus status = MS::kSuccess;

	//
	// We request the computeNodeColor attributes here to make sure that
	// they get marked clean as the arrayMapper's output attribute(s) get
	// computed.
	//
	/* MDataHandle hComputeNodeColor = */ block.inputValue( computeNodeColor );
	/* MDataHandle hComputeNodeColorR = */ block.inputValue( computeNodeColorR );
	/* MDataHandle hComputeNodeColorG = */ block.inputValue( computeNodeColorG );
	/* MDataHandle hComputeNodeColorB = */ block.inputValue( computeNodeColorB );

	bool doUcoord = false;
	bool doVcoord = false;
	bool doOutColor = ( plug.attribute() == outColorPP );
	bool doOutValue = ( plug.attribute() == outValuePP );

	// Get pointers to the source attributes: aUCoordPP, aVCoordPP.
	//
	MFnDoubleArrayData dataDoubleArrayFn;
	MObject uCoordD = block.inputValue( uCoordPP ).data();
	status = dataDoubleArrayFn.setObject( uCoordD );
	MDoubleArray uAry;
	if( status == MS::kSuccess )
	{
		uAry = dataDoubleArrayFn.array();
		if (uAry.length())
		{
			doUcoord = true;
		}
	}

	status = MS::kSuccess;
	MObject vCoordD = block.inputValue( vCoordPP ).data();
	status = dataDoubleArrayFn.setObject( vCoordD );
	MDoubleArray vAry;
	if( status == MS::kSuccess )
	{
		vAry = dataDoubleArrayFn.array();
		if (vAry.length())
		{
			doVcoord = true;
		}
	}

	// Get pointers to destination attributes: outColorPP, outValuePP.
	//
	status = MS::kSuccess;
	MFnVectorArrayData dataVectorArrayFn;
	MVectorArray outColorAry;
	if( doOutColor )
	{
		MObject colorD = block.outputValue( outColorPP ).data();
		status = dataVectorArrayFn.setObject( colorD );

		if( status == MS::kSuccess )
		{
			outColorAry = dataVectorArrayFn.array();
		}
	}

	status = MS::kSuccess;
	MDoubleArray outValueAry;
	if( doOutValue )
	{
		MObject valueD = block.outputValue( outValuePP ).data();
		status = dataDoubleArrayFn.setObject( valueD );

		if( status == MS::kSuccess )
		{
			outValueAry = dataDoubleArrayFn.array();
		}
	}

	// Setup loop counter.
	//
	unsigned int uCount = ( doUcoord ? uAry.length() : 0 );
	unsigned int vCount = ( doVcoord ? vAry.length() : 0 );
	unsigned int count  = ( (uCount) > (vCount) ? (uCount) : (vCount) );

	// Resize the output arrays to match the max size
	// of the input arrays.
	//
	if( doOutColor ) outColorAry.setLength( count );
	if( doOutValue ) outValueAry.setLength( count );

	// Check for a texture node
	//
	bool hasTextureNode =
		MDynamicsUtil::hasValidDynamics2dTexture( thisMObject(),
												computeNode );

	// Call the texture node to compute color values for
	// the input u,v coordinate arrays.
	//
	if( hasTextureNode )
	{
		MDynamicsUtil::evalDynamics2dTexture( thisMObject(),
											computeNode,
											uAry,
											vAry,
											&outColorAry,
											&outValueAry );

		// Scale the output value and/or color to the min/max range.
		//
		const double& minValue = block.inputValue( outMinValue ).asDouble();
		const double& maxValue = block.inputValue( outMaxValue ).asDouble();

		if( (minValue != 0.0) || (maxValue != 1.0) )
		{
			double r = maxValue - minValue;
			if( doOutValue )
			{
				for( unsigned int i = 0; i < count; i++ )
				{
					outValueAry[i] = (r * outValueAry[i]) + minValue;
				}
			}

			if( doOutColor )
			{
				MVector minVector( minValue, minValue, minValue );
				for( unsigned int i = 0; i < count; i++ )
				{
					outColorAry[i] = (r * outColorAry[i]) + minVector;
				}
			}
		}
	}
	else
	{
		// Not connected to texture node, so simply set output
		// arrays to default values.
		//
		for( unsigned int i = 0; i < count; i++ )
		{
			MVector clr( 0.0, 0.0, 0.0 );
			if( doOutColor )
			{
				outColorAry[i] = clr;
			}
			if( doOutValue )
			{
				outValueAry[i] = clr.x;
			}
		}
	}
	dataVectorArrayFn.set( outColorAry );
	dataDoubleArrayFn.set( outValueAry );
	return MS::kSuccess;
}

MStatus particleAttrNode::computeMesh( const MPlug& plug, MDataBlock& block )
//
//	Description:
//
//		To illustrate a custom use of the arrayMapper node, we'll define this
//	alternative compute that will map particles to mesh vertex positions. If
//	array lengths do not match, we'll wrap around the arrays.
//
//	How to use:
//
//		1) Create a poly surface.
//		2) connectAttr polyShape1.outMesh particleAttr1.computeNode
//		3) connectAttr particleShape1.count particleAttr1.particleCount
//		4) connectAttr particleAttr1.outPosition particleShape1.rampPosition (*)
//
//		* You may choose to drive any vector based per particle attribute.
//		  rampPosition was selected here as an example.
//
{
	MStatus status = MS::kSuccess;

	// Verify that computeNode has a mesh connected to computeNode
	//
	MPlug compPlug( thisMObject(), computeNode );
	MPlugArray conns;
	compPlug.connectedTo( conns, true, false, &status );
	if( conns.length() <= 0 )
	{
		return MS::kFailure;
	}
	MPlug conn = conns[0];
	MObject compNode = conn.node();
	MFnMesh meshFn( compNode, &status );
	if( status != MS::kSuccess )
	{
		return MS::kFailure;
	}

	// Retrieve the mesh vertices
	//
	MPointArray points;
	meshFn.getPoints( points );
	unsigned int nPoints = points.length();

	// Retrieve the current particle count.
	//
	// NOTE: Due to the order in which the particle system requests
	// various pieces of data, some attributes are requested prior
	// to the actual emission of particles (eg. rampPosition), whereas
	// other attributes are requested after particles have been emitted.
	//
	// If the driven PP attribute on the particleShape is requested prior
	// to the emission of particles, this compute() method will be called
	// before any particles have been emitted. As a result, the effect
	// will be lagged by one frame.
	//
	unsigned int nParticles = 0;
	int nSignedPart = block.inputValue( particleCount ).asInt();
	if( nSignedPart > 0 )
	{
		nParticles = nSignedPart;
	}

	// Get pointer to destination attribute: outPositionPP
	//
	MFnVectorArrayData dataVectorArrayFn;
	MVectorArray outPosArray;
	MObject posD = block.outputValue( outPositionPP ).data();
	//const char* typeStr = posD.apiTypeStr();
	status = dataVectorArrayFn.setObject( posD );
	if( status == MS::kSuccess )
	{
		outPosArray = dataVectorArrayFn.array();
	}

	outPosArray.setLength( nParticles );
	for( unsigned int i = 0; i < nParticles; i++ )
	{
		unsigned int index = i;
		if( nParticles > nPoints )
		{
			index = i % nPoints;
		}
		MPoint point = points[index];
		MVector pos( point.x, point.y, point.z );
		outPosArray[i] = pos;
	}
	dataVectorArrayFn.set( outPosArray );
	return MS::kSuccess;
}

