//-
// ==========================================================================
// 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/MIOStream.h>
#include <math.h>

#include <torusField.h>

#include <maya/MTime.h>
#include <maya/MVectorArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MMatrix.h>
#include <maya/MArrayDataBuilder.h>

#include <maya/MFnDependencyNode.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnCompoundAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnVectorArrayData.h>
#include <maya/MFnDoubleArrayData.h>
#include <maya/MFnMatrixData.h>


MObject torusField::aMinDistance;
MObject torusField::aAttractDistance;
MObject torusField::aRepelDistance;
MObject torusField::aDrag;
MObject torusField::aSwarmAmplitude;
MObject torusField::aSwarmFrequency;
MObject torusField::aSwarmPhase;

MTypeId torusField::id( 0x80018 );


void *torusField::creator()
{
    return new torusField;
}

MStatus torusField::initialize()
//
//	Descriptions:
//		Initialize the node, attributes.
//
{
	MStatus status;

	MFnNumericAttribute numAttr;

	// create the field basic attributes.
	//
	aMinDistance = numAttr.create("minDistance","mnd",MFnNumericData::kDouble);
	numAttr.setDefault( 0.0 );
	numAttr.setKeyable( true );
	status = addAttribute( aMinDistance );
	McheckErr(status, "ERROR adding aMinDistance attribute.\n");

	aAttractDistance = numAttr.create("attractDistance","ad",
										MFnNumericData::kDouble);
	numAttr.setDefault( 20.0 );
	numAttr.setKeyable( true );
	status = addAttribute( aAttractDistance );
	McheckErr(status, "ERROR adding aAttractDistance attribute.\n");

	aRepelDistance = numAttr.create("repelDistance","rd",
										MFnNumericData::kDouble);
	numAttr.setDefault( 10.0 );
	numAttr.setKeyable( true );
	status = addAttribute( aRepelDistance );
	McheckErr(status, "ERROR adding aRepelDistance attribute.\n");

	aDrag = numAttr.create("drag", "d", MFnNumericData::kDouble);
	numAttr.setDefault( 0.0 );
	numAttr.setKeyable( true );
	status = addAttribute( aDrag );
	McheckErr(status, "ERROR adding aDrag attribute.\n");

	aSwarmAmplitude = numAttr.create("swarmAmplitude", "samp",
										MFnNumericData::kDouble);
	numAttr.setDefault( 0.0 );
	numAttr.setKeyable( true );
	status = addAttribute( aSwarmAmplitude );
	McheckErr(status, "ERROR adding aSwarmAmplitude attribute.\n");

	aSwarmFrequency = numAttr.create("swarmFrequency", "sfrq",
										MFnNumericData::kDouble);
	numAttr.setDefault( 1.0 );
	numAttr.setKeyable( true );
	status = addAttribute( aSwarmFrequency );
	McheckErr(status, "ERROR adding aSwarmFrequency attribute.\n");

	aSwarmPhase = numAttr.create("swarmPhase", "sa", MFnNumericData::kDouble);
	numAttr.setDefault( 0.0 );
	numAttr.setKeyable( true );
	status = addAttribute( aSwarmPhase );
	McheckErr(status, "ERROR adding aSwarmPhase attribute.\n");

	// the new attribute will affect output force.
	//
	//status = attributeAffects( aMinDistance, outputForce );
	//McheckErr(status, "ERROR in attributeAffects(aMinDistance,outputForce).\n");
	//status = attributeAffects( aAttractDistance, outputForce );
	//McheckErr(status, "ERROR in attributeAffects(aAttractDistance,outputForce).\n");
	//status = attributeAffects( aRepelDistance, outputForce );
	//McheckErr(status, "ERROR in attributeAffects(aRepelDistance,outputForce).\n");
	//status = attributeAffects( aDrag, outputForce );
	//McheckErr(status, "ERROR in attributeAffects(aDrag,outputForce).\n");

	return( MS::kSuccess );
}


MStatus torusField::compute(const MPlug& plug, MDataBlock& block)
//
//	Descriptions:
//		compute output force.
//
{
	MStatus status;

	if( !(plug == mOutputForce) )
        return( MS::kUnknownParameter );

	// get the logical index of the element this plug refers to.
	//
	int multiIndex = plug.logicalIndex( &status );
	McheckErr(status, "ERROR in plug.logicalIndex.\n");

	// Get input data handle, use outputArrayValue since we do not
	// want to evaluate both inputs, only the one related to the
	// requested multiIndex. Evaluating both inputs at once would cause
	// a dependency graph loop.
	//
	MArrayDataHandle hInputArray = block.outputArrayValue( mInputData, &status );
	McheckErr(status,"ERROR in hInputArray = block.outputArrayValue().\n");

	status = hInputArray.jumpToElement( multiIndex );
	McheckErr(status, "ERROR: hInputArray.jumpToElement failed.\n");

	// get children of aInputData.
	//
	MDataHandle hCompond = hInputArray.inputValue( &status );
	McheckErr(status, "ERROR in hCompond=hInputArray.inputValue\n");

	MDataHandle hPosition = hCompond.child( mInputPositions );
	MObject dPosition = hPosition.data();
	MFnVectorArrayData fnPosition( dPosition );
	MVectorArray points = fnPosition.array( &status );
	McheckErr(status, "ERROR in fnPosition.array(), not find points.\n");

	MDataHandle hVelocity = hCompond.child( mInputVelocities );
	MObject dVelocity = hVelocity.data();
	MFnVectorArrayData fnVelocity( dVelocity );
	MVectorArray velocities = fnVelocity.array( &status );
	McheckErr(status, "ERROR in fnVelocity.array(), not find velocities.\n");

	MDataHandle hMass = hCompond.child( mInputMass );
	MObject dMass = hMass.data();
	MFnDoubleArrayData fnMass( dMass );
	MDoubleArray masses = fnMass.array( &status );
	McheckErr(status, "ERROR in fnMass.array(), not find masses.\n");

	// Compute the output force.
	//

	MVectorArray forceArray;
	bool useMaxDistSet = useMaxDistanceValue( block );
	if( useMaxDistSet )
	{
		applyMaxDist( block, points, velocities, masses, forceArray );
	}
	else
	{
		applyNoMaxDist( block, points, velocities, masses, forceArray );
	}

	// get output data handle
	//
	MArrayDataHandle hOutArray = block.outputArrayValue( mOutputForce, &status);
	McheckErr(status, "ERROR in hOutArray = block.outputArrayValue.\n");
	MArrayDataBuilder bOutArray = hOutArray.builder( &status );
	McheckErr(status, "ERROR in bOutArray = hOutArray.builder.\n");

	// get output force array from block.
	//
	MDataHandle hOut = bOutArray.addElement(multiIndex, &status);
	McheckErr(status, "ERROR in hOut = bOutArray.addElement.\n");

	MFnVectorArrayData fnOutputForce;
	MObject dOutputForce = fnOutputForce.create( forceArray, &status );
	McheckErr(status, "ERROR in dOutputForce = fnOutputForce.create\n");

	// update data block with new output force data.
	//
	hOut.set( dOutputForce );
	block.setClean( plug );

	return( MS::kSuccess );
}


void torusField::applyNoMaxDist
	(
		MDataBlock &block,				// get field param from this block
		const MVectorArray &points,		// current position of Object
		const MVectorArray &velocities,	// current velocity of Object
		const MDoubleArray &/*masses*/,		// mass of Object
		MVectorArray &outputForce		// output force
	)
//
//	Descriptions:
//		Compute output force in the case that the useMaxDistance is not set.
//
{
	// points and velocities should have the same length. If not return.
	//
	if( points.length() != velocities.length() )
		return;

	// clear the output force array.
	//
	outputForce.clear();

	// get field parameters.
	//
	double magValue = magnitudeValue( block );
	// double attenValue = attenuationValue( block );
	double minDist = minDistanceValue( block );
	double attractDist = attractDistanceValue( block );
	double repelDist = repelDistanceValue( block );
	double dragMag = dragValue( block );
	double swarmAmp = swarmAmplitudeValue( block );

	// get owner's data. posArray may have only one point which is the centroid
	// (if this has owner) or field position(if without owner). Or it may have
	// a list of points if with owner and applyPerVertex.
	//
	MVectorArray posArray;
	posArray.clear();
	ownerPosition( block, posArray );

	int fieldPosCount = posArray.length();
	int receptorSize = points.length();

	// With this model,if max distance isn't set then we
	// also don't attenuate, because 1 - dist/maxDist isn't
	// meaningful. No max distance and no attenuation.
	//
	if (dragMag <= 0)
	{
		if (swarmAmp <= 0)
		{
			// No max dist, no attenuation, no drag, no swarm
			//
			for (int ptIndex = 0; ptIndex < receptorSize; ptIndex ++ )
			{
				MVector forceV(0.0,0.0,0.0);
				const MVector &receptorPoint = points[ptIndex];

				// Apply from every field position to every receptor position.
				//
				for(int i = fieldPosCount; --i>=0; )
				{
					MVector difference = (receptorPoint-posArray[i]);

					double distance = difference.length();
					if (distance < minDist) continue;

					if (distance <= repelDist)
						forceV += difference * magValue;
					else if (distance >= attractDist)
						forceV += -difference * magValue;
				}
				outputForce.append( forceV );
			}
		}
		else
		{
			// No max dist, no attenuation, no drag, yes swarm
			//
			for (int ptIndex = 0; ptIndex < receptorSize; ptIndex ++ )
			{
				MVector forceV(0.0,0.0,0.0);
				const MVector &receptorPoint = points[ptIndex];

				// Apply from every field position to every receptor position.
				//
				double distance = 0.0;
				int i;
				for(i = fieldPosCount; --i>=0; )
				{
					MVector difference = (receptorPoint-posArray[i]);
					distance = difference.length();
					if (distance < minDist) continue;

					if (distance <= repelDist)
						forceV += difference * magValue;
					else if (distance >= attractDist)
						forceV += -difference * magValue;
				}

				// Apply swarm only if the object is inside the zone
				// the repulsion-attraction is pushing the object to.
				//
				if ( distance >= repelDist && distance <= attractDist)
				{
					double frequency = swarmFrequencyValue( block );
					MVector phase( 0.0, 0.0, swarmPhaseValue(block) );

					// Add swarm in here
					//
					for(i = fieldPosCount; --i >= 0;)
					{
						MVector difference = receptorPoint - posArray[i];
						difference = (difference + phase) * frequency;

						double *noiseEffect = &difference.x;
						if( (noiseEffect[0] < -2147483647.0) ||
							(noiseEffect[0] >  2147483647.0) ||
							(noiseEffect[1] < -2147483647.0) ||
							(noiseEffect[1] >  2147483647.0) ||
							(noiseEffect[2] < -2147483647.0) ||
							(noiseEffect[2] >  2147483647.0) )
							continue;

						double noiseOut[4];
						noiseFunction( noiseEffect, noiseOut );
			 			MVector swarmForce( noiseOut[0] * swarmAmp,
											noiseOut[1] * swarmAmp,
											noiseOut[2] * swarmAmp );
						forceV += swarmForce;
					}
				}
				outputForce.append( forceV );
			}
		}
	}
	else
	{
		if (swarmAmp <= 0)
		{
			// Yes drag, no swarm
			//
			for (int ptIndex = 0; ptIndex < receptorSize; ptIndex ++ )
			{
				const MVector& receptorPoint = points[ptIndex];

				// Apply from every field position to every receptor position.
				//
				MVector forceV(0,0,0);
				double distance = 0.0;
				for(int i = fieldPosCount; --i>=0; )
				{
					MVector difference = (receptorPoint-posArray[i]);
					distance = difference.length();
					if (distance < minDist) continue;

					if (distance <= repelDist)
						forceV += difference * magValue;
					else if (distance >= attractDist)
						forceV += -difference * magValue;
				}

				// Apply drag only if the object is inside the zone
				// the repulsion-attraction is pushing the object to.
				//
				if ( distance >= repelDist && distance <= attractDist)
				{
					if (fieldPosCount > 0)
					{
						MVector dragForceV;
						dragForceV = velocities[ptIndex] *
												(-dragMag) * fieldPosCount;
						forceV += dragForceV;
					}
				}

				outputForce.append( forceV );
			}
		}
		else
		{
			// Yes drag, yes swarm
			//
			for (int ptIndex = 0; ptIndex < receptorSize; ptIndex ++ )
			{
				const MVector &receptorPoint = points[ptIndex];

				// Apply from every field position to every receptor position.
				//
				MVector forceV(0,0,0);
				double distance = 0.0;
				int i;
				for(i = fieldPosCount; --i>=0; )
				{
					MVector difference = (receptorPoint-posArray[i]);
					distance = difference.length();
					if (distance < minDist) continue;

					if (distance <= repelDist)
						forceV += difference * magValue;
					else if (distance >= attractDist)
						forceV += -difference * magValue;
				}

				// Apply drag and swarm only if the object is inside
				// the zone the repulsion-attraction is pushing the object to.
				//
				if ( distance >= repelDist && distance <= attractDist)
				{
					if (fieldPosCount > 0)
					{
						MVector dragForceV;
						dragForceV = velocities[ptIndex] *
												(-dragMag) * fieldPosCount;
						forceV += dragForceV;
					}

					// Add swarm in here
					//
					double frequency = swarmFrequencyValue( block );
					MVector phase( 0.0, 0.0, swarmPhaseValue(block) );

					for(i = fieldPosCount; --i>=0; )
					{
						MVector difference = receptorPoint - posArray[i];
						difference = (difference + phase) * frequency;

						double *noiseEffect = &difference.x;
						if( (noiseEffect[0] < -2147483647.0) ||
							(noiseEffect[0] >  2147483647.0) ||
							(noiseEffect[1] < -2147483647.0) ||
							(noiseEffect[1] >  2147483647.0) ||
							(noiseEffect[2] < -2147483647.0) ||
							(noiseEffect[2] >  2147483647.0) )
							continue;

						double noiseOut[4];
						noiseFunction( noiseEffect, noiseOut );
						MVector swarmForce( noiseOut[0] * swarmAmp,
											noiseOut[1] * swarmAmp,
											noiseOut[2] * swarmAmp );
						forceV += swarmForce;
					}
				}
				outputForce.append( forceV );
			}
		}
	}
}


void torusField::applyMaxDist
	(
		MDataBlock& block,				// get field param from this block
		const MVectorArray &points,		// current position of Object
		const MVectorArray &velocities,	// current velocity of Object
		const MDoubleArray &/*masses*/,		// mass of Object
		MVectorArray &outputForce		// output force
	)
//
//	Descriptions:
//		Compute output force in the case that the useMaxDistance is set.
//
{
	// points and velocities should have the same length. If not return.
	//
	if( points.length() != velocities.length() )
		return;

	// clear the output force array.
	//
	outputForce.clear();

	// get field parameters.
	//
	double magValue = magnitudeValue( block );
	double attenValue = attenuationValue( block );
	double maxDist = maxDistanceValue( block );
	double minDist = minDistanceValue( block );
	double attractDist = attractDistanceValue( block );
	double repelDist = repelDistanceValue( block );
	double dragMag = dragValue( block );
	double swarmAmp = swarmAmplitudeValue( block );

	// get owner's data. posArray may have only one point which is the centroid
	// (if this has owner) or field position(if without owner). Or it may have
	// a list of points if with owner and applyPerVertex.
	//
	MVectorArray posArray;
	posArray.clear();
	ownerPosition( block, posArray );

	int fieldPosCount = posArray.length();
	int receptorSize = points.length();

	if (attenValue > 0.0)
	{
		// Max distance applies and so does attenuation.
		//
		for (int ptIndex = 0; ptIndex < receptorSize; ptIndex ++ )
		{
			const MVector &receptorPoint = points[ptIndex];

			// Apply from every field position to every receptor position.
			//
			MVector forceV(0,0,0);
			MVector sumForceV(0,0,0);
			for(int i = fieldPosCount; --i>=0; )
			{
				MVector difference = receptorPoint-posArray[i];
				double distance  = difference.length();
				if (distance <= maxDist && distance >= minDist )
				{
					double force = magValue *
									(pow((1.0-(distance/maxDist)),attenValue));
					forceV = difference * force;

					// Apply drag and swarm if the object is inside
					// the zone the repulsion-attraction is pushing the
					// object to, and if they are set.
					//
					if ( distance >= repelDist && distance <= attractDist)
					{
						if (fieldPosCount > 0 && dragMag > 0)
						{
							MVector dragForceV;
							dragForceV = velocities[ptIndex] *
											(-dragMag) * fieldPosCount;
							forceV += dragForceV;
						}

						// Add swarm if swarm aplitude is set.
						//
						if (swarmAmp > 0)
						{
							double frequency = swarmFrequencyValue( block );
							MVector phase( 0.0, 0.0, swarmPhaseValue(block) );

							difference = receptorPoint - posArray[i];
							difference = (difference + phase) * frequency;
							double *noiseEffect = &difference.x;
							if( (noiseEffect[0] < -2147483647.0) ||
								(noiseEffect[0] >  2147483647.0) ||
								(noiseEffect[1] < -2147483647.0) ||
								(noiseEffect[1] >  2147483647.0) ||
								(noiseEffect[2] < -2147483647.0) ||
								(noiseEffect[2] >  2147483647.0) )
								continue;

							double noiseOut[4];
							noiseFunction( noiseEffect, noiseOut );
							MVector swarmForce( noiseOut[0] * swarmAmp,
												noiseOut[1] * swarmAmp,
												noiseOut[2] * swarmAmp );
							forceV += swarmForce;
						}
					}
				}
				if (maxDist > 0.0) forceV *= falloffCurve(distance/maxDist);
				sumForceV += forceV;					
			}
			outputForce.append( sumForceV );
		}
	}
	else
	{
		// Max dist applies, but not attenuation.
		//
		for (int ptIndex = 0; ptIndex < receptorSize; ptIndex ++ )
		{
			const MVector & receptorPoint = points[ptIndex];

			// Apply from every field position to every receptor position.
			//
			MVector forceV(0,0,0);
			MVector sumForceV(0,0,0);
			int i;
			for(i = fieldPosCount; --i>=0; )
			{
				MVector difference = (receptorPoint-posArray[i]);
				double distance = difference.length();
				if (distance < minDist || distance > maxDist) continue;

				if (distance <= repelDist)
					forceV = difference * magValue;
				else if (distance >= attractDist)
					forceV = -difference * magValue;

				// Apply drag and swarm if the object is inside
				// the zone the repulsion-attraction is pushing the
				// object to, and if they are set.
				//
				if ( distance >= repelDist && distance <= attractDist)
				{
					if (fieldPosCount > 0 && dragMag > 0)
					{
						MVector dragForceV;
						dragForceV = velocities[ptIndex] *
											(-dragMag) * fieldPosCount;
						forceV += dragForceV;
					}

					// Add swarm if swarm aplitude is set.
					//
					if (swarmAmp > 0)
					{
						double frequency = swarmFrequencyValue( block );
						MVector phase( 0.0, 0.0, swarmPhaseValue(block) );

						for(i = fieldPosCount; --i >= 0;)
						{
							difference = receptorPoint - posArray[i];
							difference = (difference + phase) * frequency;
							double *noiseEffect = &difference.x;
							if( (noiseEffect[0] < -2147483647.0) ||
								(noiseEffect[0] >  2147483647.0) ||
								(noiseEffect[1] < -2147483647.0) ||
								(noiseEffect[1] >  2147483647.0) ||
								(noiseEffect[2] < -2147483647.0) ||
								(noiseEffect[2] >  2147483647.0) )
								continue;

							double noiseOut[4];
							noiseFunction( noiseEffect, noiseOut );
							MVector swarmForce( noiseOut[0] * swarmAmp,
												noiseOut[1] * swarmAmp,
												noiseOut[2] * swarmAmp );

							forceV += swarmForce;
						}
					}
				}
				if (maxDist > 0.0) forceV *= falloffCurve(distance/maxDist);
				sumForceV += forceV;
			}
			outputForce.append( sumForceV );
		}
	}
}


void torusField::ownerPosition
	(
		MDataBlock& block,
		MVectorArray &ownerPosArray
	)
//
//	Descriptions:
//		If this field has an owner, get the owner's position array or
//		centroid, then assign it to the ownerPosArray.
//		If it does not have owner, get the field position in the world
//		space, and assign it to the given array, ownerPosArray.
//
{
	MStatus status;

	if( applyPerVertexValue(block) )
	{
		MDataHandle hOwnerPos = block.inputValue( mOwnerPosData, &status );
		if( status == MS::kSuccess )
		{
			MObject dOwnerPos = hOwnerPos.data();
			MFnVectorArrayData fnOwnerPos( dOwnerPos );
			MVectorArray posArray = fnOwnerPos.array( &status );
			if( status == MS::kSuccess )
			{
				// assign vectors from block to ownerPosArray.
				//
				for( unsigned int i = 0; i < posArray.length(); i ++ )
					ownerPosArray.append( posArray[i] );
			}
			else
			{
				MVector worldPos(0.0, 0.0, 0.0);
				//status = getWorldPosition( block, worldPos );
				status = getWorldPosition( worldPos );
				ownerPosArray.append( worldPos );
			}
		}
		else
		{
			// get the field position in the world space
			// and add it into ownerPosArray.
			//
			MVector worldPos(0.0, 0.0, 0.0);
			//status = getWorldPosition( block, worldPos );
			status = getWorldPosition( worldPos );
			ownerPosArray.append( worldPos );
		}
	}
	else
	{
		MVector centroidV(0.0, 0.0, 0.0);
		status = ownerCentroidValue( block, centroidV );
		if( status == MS::kSuccess )
		{
			// assign centroid vector to ownerPosArray.
			//
			ownerPosArray.append( centroidV );
		}
		else
		{
			// get the field position in the world space.
			//
			MVector worldPos(0.0, 0.0, 0.0);
			//status = getWorldPosition( block, worldPos );
			status = getWorldPosition( worldPos );
			ownerPosArray.append( worldPos );
		}
	}
}


MStatus torusField::getWorldPosition( MVector &vector )
//
//	Descriptions:
//		get the field position in the world space.
//		The position value is from inherited attribute, aWorldMatrix.
//
{
	MStatus status;

	MObject thisNode = thisMObject();
	MFnDependencyNode fnThisNode( thisNode );

	// get worldMatrix attribute.
	//
	MObject worldMatrixAttr = fnThisNode.attribute( "worldMatrix" );

	// build worldMatrix plug, and specify which element the plug refers to.
	// We use the first element(the first dagPath of this field).
	//
	MPlug matrixPlug( thisNode, worldMatrixAttr );
	matrixPlug = matrixPlug.elementByLogicalIndex( 0 );

	// Get the value of the 'worldMatrix' attribute
	//
	MObject matrixObject;
	status = matrixPlug.getValue( matrixObject );
	if( !status )
	{
		status.perror("torusField::getWorldPosition: get matrixObject");
		return( status );
	}

	MFnMatrixData worldMatrixData( matrixObject, &status );
	if( !status )
	{
		status.perror("torusField::getWorldPosition: get worldMatrixData");
		return( status );
	}

	MMatrix worldMatrix = worldMatrixData.matrix( &status );
	if( !status )
	{
		status.perror("torusField::getWorldPosition: get worldMatrix");
		return( status );
	}

	// assign the translate to the given vector.
	//
	vector[0] = worldMatrix( 3, 0 );
	vector[1] = worldMatrix( 3, 1 );
	vector[2] = worldMatrix( 3, 2 );

    return( status );
}


MStatus torusField::getWorldPosition( MDataBlock& block, MVector &vector )
//
//	Descriptions:
//		Find the field position in the world space.
//
{
    MStatus status;

	MObject thisNode = thisMObject();
	MFnDependencyNode fnThisNode( thisNode );

	// get worldMatrix attribute.
	//
	MObject worldMatrixAttr = fnThisNode.attribute( "worldMatrix" );

	// build worldMatrix plug, and specify which element the plug refers to.
	// We use the first element(the first dagPath of this field).
	//
	MPlug matrixPlug( thisNode, worldMatrixAttr );
	matrixPlug = matrixPlug.elementByLogicalIndex( 0 );

    //MDataHandle hWMatrix = block.inputValue( worldMatrix, &status );
    MDataHandle hWMatrix = block.inputValue( matrixPlug, &status );

	McheckErr(status, "ERROR getting hWMatrix from dataBlock.\n");

    if( status == MS::kSuccess )
    {
        MMatrix wMatrix = hWMatrix.asMatrix();
        vector[0] = wMatrix(3, 0);
        vector[1] = wMatrix(3, 1);
        vector[2] = wMatrix(3, 2);
    }
    return( status );
}

MStatus torusField::getForceAtPoint(const MVectorArray&	points,
                                  const MVectorArray&	velocities,
                                  const MDoubleArray&	masses,
                                  MVectorArray&	forceArray,
                                  double	/*deltaTime*/)
//
//    This method is not required to be overridden, it is only necessary
//    for compatibility with the MFnField function set.
//
{
	MDataBlock block = forceCache();

	bool useMaxDistSet = useMaxDistanceValue( block );
	if( useMaxDistSet )
	{
		applyMaxDist( block, points, velocities, masses, forceArray );
	}
	else
	{
		applyNoMaxDist( block, points, velocities, masses, forceArray );
	}

    return MS::kSuccess;
}

MStatus torusField::iconSizeAndOrigin(	GLuint& width,
					GLuint& height,
					GLuint& xbo,
					GLuint& ybo   )
//
//	This method is not required to be overridden.  It should be overridden
//	if the plug-in has custom icon.
//
//	The width and height have to be a multiple of 32 on Windows and 16 on 
//	other platform.
//
//	Define an 8x8 icon at the lower left corner of the 32x32 grid. 
//	(xbo, ybo) of (4,4) makes sure the icon is center at origin.
//
{
	width = 32;
	height = 32;
	xbo = 4;
	ybo = 4;
	return MS::kSuccess;
}

MStatus torusField::iconBitmap(GLubyte* bitmap)
//
//	This method is not required to be overridden.  It should be overridden
//	if the plug-in has custom icon.
//
//	Define an 8x8 icon at the lower left corner of the 32x32 grid. 
//	(xbo, ybo) of (4,4) makes sure the icon is center at origin.
{
	bitmap[0] = 0x18;
	bitmap[4] = 0x66;
	bitmap[8] = 0xC3;
	bitmap[12] = 0x81;
	bitmap[16] = 0x81;
	bitmap[20] = 0xC3;
	bitmap[24] = 0x66;
	bitmap[28] = 0x18;

	return MS::kSuccess;
}

#define rand3a(x,y,z)	frand(67*(x)+59*(y)+71*(z))
#define rand3b(x,y,z)	frand(73*(x)+79*(y)+83*(z))
#define rand3c(x,y,z)	frand(89*(x)+97*(y)+101*(z))
#define rand3d(x,y,z)	frand(103*(x)+107*(y)+109*(z))

int		xlim[3][2];		// integer bound for point
double	xarg[3];		// fractional part

double frand( register int s )   // get random number from seed
{
	s = s << 13^s;
	return(1. - ((s*(s*s*15731+789221)+1376312589)&0x7fffffff)/1073741824.);
}

double hermite( double p0, double p1, double r0, double r1, double t )
{
	register double t2, t3, _3t2, _2t3 ;
	t2 = t * t;
	t3 = t2 * t;
	_3t2 = 3. * t2;
	_2t3 = 2. * t3 ;

	return(p0*(_2t3-_3t2+1) + p1*(-_2t3+_3t2) + r0*(t3-2.*t2+t) + r1*(t3-t2));
}

void interpolate( double f[4], register int i, register int n )
//
//	f[] returned tangent and value *
//	i   location ?
//	n   order
//
{
	double f0[4], f1[4] ;  //results for first and second halves

	if( n == 0 )	// at 0, return lattice value
	{
		f[0] = rand3a( xlim[0][i&1], xlim[1][i>>1&1], xlim[2][i>>2] );
		f[1] = rand3b( xlim[0][i&1], xlim[1][i>>1&1], xlim[2][i>>2] );
		f[2] = rand3c( xlim[0][i&1], xlim[1][i>>1&1], xlim[2][i>>2] );
		f[3] = rand3d( xlim[0][i&1], xlim[1][i>>1&1], xlim[2][i>>2] );
		return;
	}

	n--;
	interpolate( f0, i, n );		// compute first half
	interpolate( f1, i| 1<<n, n );	// compute second half

	// use linear interpolation for slopes
	//
	f[0] = (1. - xarg[n]) * f0[0] + xarg[n] * f1[0];
	f[1] = (1. - xarg[n]) * f0[1] + xarg[n] * f1[1];
	f[2] = (1. - xarg[n]) * f0[2] + xarg[n] * f1[2];

	// use hermite interpolation for values
	//
	f[3] = hermite( f0[3], f1[3], f0[n], f1[n], xarg[n] );
}

void torusField::noiseFunction( double *inNoise, double *out )
//
//	Descriptions:
//		A noise function.
//
{
	xlim[0][0] = (int)floor( inNoise[0] );
	xlim[0][1] = xlim[0][0] + 1;
	xlim[1][0] = (int)floor( inNoise[1] );
	xlim[1][1] = xlim[1][0] + 1;
	xlim[2][0] = (int)floor( inNoise[2] );
	xlim[2][1] = xlim[2][0] + 1;

	xarg[0] = inNoise[0] - xlim[0][0];
	xarg[1] = inNoise[1] - xlim[1][0];
	xarg[2] = inNoise[2] - xlim[2][0];

	interpolate( out, 0, 3 ) ;
}

#define TORUS_PI 3.14159265
#define TORUS_2PI 2*TORUS_PI
#define EDGES 30
#define SEGMENTS 20

//
//	Descriptions:
//		Draw a set of rings to symbolie the field. This does not override default icon, you can do that by implementing the iconBitmap() function
//
void torusField::draw( M3dView& view, const MDagPath& path, M3dView::DisplayStyle style, M3dView:: DisplayStatus )
{
	 view.beginGL();
	 for (int j = 0; j < SEGMENTS; j++ )
	 {
		glPushMatrix();
		glRotatef( GLfloat(360 * j / SEGMENTS), 0.0, 1.0, 0.0 );
		glTranslatef( 1.5, 0.0, 0.0 );

		 for (int i = 0; i < EDGES; i++ )
		 {
			glBegin(GL_LINE_STRIP);
			float p0 = float( TORUS_2PI * i / EDGES );
			float p1 = float( TORUS_2PI * (i+1) / EDGES );
			glVertex2f( cos(p0), sin(p0) );
			glVertex2f( cos(p1), sin(p1) );
			glEnd();
		 }
		glPopMatrix();
	 }
	 view.endGL ();
}


MStatus initializePlugin(MObject obj)
{
	MStatus status;
	MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");

	status = plugin.registerNode( "torusField", torusField::id,
							&torusField::creator, &torusField::initialize,
							MPxNode::kFieldNode );
	if (!status) {
		status.perror("registerNode");
		return status;
	}

	return status;
}

MStatus uninitializePlugin(MObject obj)
{
	MStatus status;
	MFnPlugin plugin(obj);

	status = plugin.deregisterNode( torusField::id );
	if (!status) {
		status.perror("deregisterNode");
		return status;
	}

	return status;
}

