//-
// ==========================================================================
// 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 <maya/MStatus.h>
#include <maya/MDagPath.h>
#include <maya/MFloatPointArray.h>
#include <maya/MObject.h>
#include <maya/MPlugArray.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnMesh.h>
#include <maya/MHairSystem.h>
#include <maya/MFnPlugin.h>

#include <math.h> 

#define	kPluginName		"hairCollisionSolver"

//
//
// OVERVIEW:
// This plug-in implements a custom collision solver for Maya's dynamic
// hair system. This allows users to override the following aspects of
// Maya's dynamic hair systems:
//
//		o Override the Maya dynamic hair system's object-to-hair
//		  collision algorithm with a user-defined algorithm.
//		o Optionally perform global filtering on hair, such as freeze,
//		  smoothing, etc.
//
// It should be noted that Maya's dynamic hair system involves four
// arears of collision detection, and this plug-in is specific to the
// Hair-to-Object aspect only. The four areas collision which the Maya
// dynamic hair system involves are:
//
//		1) Hair to object collision.
//		2) Hair to implicit object collision.
//		3) Hair to ground plane collision.
//		4) Self collision between hairs.
//
// This plug-in illustrates the first case, overriding Maya's internal
// Hair-to-Object collision solver. There is currently no API for over-
// riding the other three collision solvers.
//
//
// RATIONALE:
// There are several reasons why are user may wish to override Maya's
// internal hair to object collision algorithm. These are:
//
//		1) The internal algorithm may not be accurate enough. After all,
//		   it is a simulation of real-life physics and there are tradeoffs
//		   taken to provide reasonable performance. Note that there are
//		   means of increasing the accuracy without writing a custom
//		   implementation, such as decreasing the dynamics time step, or
//		   increasing the hair width.
//		2) The built-in algorithm might be too accurate. If you only want
//		   simplified collisions, such as against a bounding box repre-
//		   sentation instead of the internal algorithm's exhasutive test-
//		   ing against each surface of the object, you could write a
//		   lighter-weight implementation.
//		3) You might have a desire to process the hairs, such as smooth
//		   them out.
//
//
// STRATEGY:
// The basic idea is to implement a custom callback which is registered
// via MHairSystem::registerCollisionSolverCollide(). Your callback will
// then be invoked in place of Maya's internal collision solver. By simply
// registering a collision solver, you can completely implement a custom
// hair-to-object solution.
//
// However, since the collision solver is called once per hair times the
// number of solver iterations, it is wise to pre-process the data if
// possible to speed up the collision tests. For this reason, you can
// assign a pre-frame callback. One approach is to create a pre-processed
// representation of your object pre-frame (e.g. an octree representation)
// and then access this representation during the collision testing.
//
// For the purposes of our simple demo plug-in, our private data will
// consist of a COLLISION_INFO which contains an array of COLLISION_OBJ
// structures, each one holding the bounding box of the object in world
// space.
//
// One issue with pre-processing the data involves managing the private
// data. A collision object could be deleted or turned off during a
// simulation. One way to cleanly manage such data is to store your
// private data on a typed attribute which is added to the node. You
// would build your private data once at the start of simulation in your
// pre-frame callback (keep track of the current time, and if the curTime
// passed in is less than what you store locally, assume the playback
// has restarted from the beginning -- or the user is being silly and
// trying to play the simulation backwards :-) Since it is relatively
// expensive to look up a dynamic attribute value, and the collide()
// callback can get triggered 1000's of times per frame, for efficiency
// you can pass back your private data as a pointer from your pre-frame
// routine, and this pointer is then passed directly into your collide()
// callback.
// 
//
typedef struct {
	int				numVerts;	// Number of vertices in object.
	double			minx;		// Bounding box minimal extrema.
	double			miny;		// Bounding box minimal extrema.
	double			minz;		// Bounding box minimal extrema.
	double			maxx;		// Bounding box maximal extrema.
	double			maxy;		// Bounding box maximal extrema.
	double			maxz;		// Bounding box maximal extrema.
} COLLISION_OBJ ;

typedef struct {
	int				numObjs;	// Number of collision objects.
	COLLISION_OBJ	*objs;		// Array of per-object info.
} COLLISION_INFO ;

//////////////////////////////////////////////////////////////////////////
//
// Synopsis:
//		bool	preFrame( hairSystem, curTime, privateData )
//
// Description:
//		This callback is invoked once at the start of each frame, allowing
//	the user to perform any pre-processing as they see fit, such as build-
//	ing or updating private collision-detection structures.
//
//		Note: it is possible for collision objects to change between
//	frames during a simulation (for example, the user could delete a col-
//	lision object), so if you choose to store your pre-processed data, it
//	is critical to track any edits or deletions to the collision object
//	to keep your pre-processed data valid.
//
//		There are lots of hints for writing an effective pre-frame call-
//	back in the STRATEGY section listed earlier in this file.
//
// Parameters:
//		MObject hairSystem	: (in)	The hair system shape node.
//		double	curTime		: (in)	Current time in seconds.
//		void	**privateData:(out)	Allows the user to return a private
//									data pointer to be passed into their
//									collision solver. If you store your
//									pre-processed data in data structure
//									which is difficult to access, such as
//									on a typed attribute, this provides
//									an easy way to provide the pointer.
//
// Returns:
//		bool	true		: Successfully performed any needed initial-
//							  isation for the hair simulation this frame.
//		bool	false		: An error was detected.
//
//////////////////////////////////////////////////////////////////////////


bool		preFrame(
				const MObject		hairSystem,
				const double		curTime,
				void				**privateData )
{
	MStatus status;

	// If you need want to perform any preprocessing on your collision
	// objects pre-frame, do it here. One option for storing the pre-
	// processed data is on a typed attribute on the hairSystem node.
	// That data could be fetched and updated here.
	//
	// In our example, we'll just compute a bounding box here and NOT use
	// attribute storage. That is an exercise for the reader.
	//
	MFnDependencyNode fnHairSystem( hairSystem, &status );
	CHECK_MSTATUS_AND_RETURN( status, false );
	fprintf( stderr,
			"preFrame: calling hairSystem node=`%s', time=%g\n",
			fnHairSystem.name().asChar(), curTime );
	MObjectArray	cols;
	MIntArray		logIdxs;
	CHECK_MSTATUS_AND_RETURN( MHairSystem::getCollisionObject( hairSystem,
			cols, logIdxs ), false );
	int nobj = cols.length();

	// Allocate private data.
	// This allows us to pre-process data on a pre-frame basis to avoid
	// calculating it per hair inside the collide() call. As noted earlier
	// we could allocate this in preFrame() and hang it off the hairSystem
	// node via a dynamic attribute.
	// Instead we'll allocate it here.
	//
	COLLISION_INFO *collisionInfo = (COLLISION_INFO *) malloc(
			sizeof( COLLISION_INFO ) );
	collisionInfo->objs = (COLLISION_OBJ *) malloc(
			nobj * sizeof( COLLISION_OBJ ) );
	collisionInfo->numObjs = nobj;

	// Create the private data that we'll make available to the collide
	// method. The data should actually be stored in a way that it can
	// be cleaned up (such as storing the pointer on the hairSystem node
	// using a dynamic attribute). As it stands right now, there is a
	// memory leak with this plug-in because the memory we're allocating
	// for the private data is never cleaned up.
	//
	// Note that when using the dynamic attribute approach, it is still
	// wise to set *privateData because this avoids the need to look up
	// the plug inside the collide() routine which is a high-traffic
	// method.
	//
	*privateData = (void *) collisionInfo;

	// Loop through the collision objects and pre-process, storing the
	// results in the collisionInfo structure.
	//
	int	   obj;
	for ( obj = 0; obj < nobj; ++obj ) {
		// Get the ith collision geometry we are connected to.
		//
		MObject colObj = cols[obj];

		// Get the DAG path for the collision object so we can transform
		// the vertices to world space.
		//
		MFnDagNode fnDagNode( colObj, &status );
		CHECK_MSTATUS_AND_RETURN( status, false );
		MDagPath path;
		status = fnDagNode.getPath( path );
		CHECK_MSTATUS_AND_RETURN( status, false );
		MFnMesh fnMesh( path, &status );
		if ( MS::kSuccess != status ) {
			fprintf( stderr,
					"%s:%d: collide was not passed a valid mesh shape\n",
					__FILE__, __LINE__ );
			return( false );
		}

		// Get the vertices of the object transformed to world space.
		//
		MFloatPointArray	verts;
		status = fnMesh.getPoints( verts, MSpace::kWorld );
		CHECK_MSTATUS_AND_RETURN( status, false );

		// Compute the bounding box for the collision object.
		// As this is a quick and dirty demo, we'll just support collisions
		// between hair and the bbox.
		//
		double minx, miny, minz, maxx, maxy, maxz, x, y, z;
		minx = maxx = verts[0].x;
		miny = maxy = verts[0].y;
		minz = maxz = verts[0].z;
		int nv = verts.length();
		int i;
		for ( i = 1; i < nv; ++i ) {
			x = verts[i].x;
			y = verts[i].y;
			z = verts[i].z;

			if ( x < minx ) {
				minx = x;
			}
			if ( y < miny ) {
				miny = y;
			}
			if ( z < minz ) {
				minz = z;
			}

			if ( x > maxx ) {
				maxx = x;
			}
			if ( y > maxy ) {
				maxy = y;
			}
			if ( z > maxz ) {
				maxz = z;
			}
		}

		// Store this precomputed informantion into our private data
		// structure.
		//
		collisionInfo->objs[obj].numVerts = nv;
		collisionInfo->objs[obj].minx = minx;
		collisionInfo->objs[obj].miny = miny;
		collisionInfo->objs[obj].minz = minz;
		collisionInfo->objs[obj].maxx = maxx;
		collisionInfo->objs[obj].maxy = maxy;
		collisionInfo->objs[obj].maxz = maxz;
		fprintf( stderr, "Inside preFrameInit, bbox=%g %g %g %g %g %g\n",
				minx,miny,minz,maxx,maxy,maxz);
	}

	return( true );
}

//////////////////////////////////////////////////////////////////////////
//
// Synopsis:
//		bool	belowCollisionPlane( co, pnt )
//
// Description:
//		Test if `pnt' is directly below the collision plane of `co'.
//
// Parameters:
//		COLLISION_OBJ	*co	: (in)	The collision object to test against.
//		MVector			&pnt: (in)	Point to test.
//
// Returns:
//		bool	true		: The `pnt' is directly below the collision
//							  plane specified by `co'.
//		bool	false		: The `pnt' is not directly below the collis-
//							  ion plane specified by `co'.
//
//////////////////////////////////////////////////////////////////////////


bool	belowCollisionPlane( const COLLISION_OBJ *co, const MVector &pnt )
{
	return(    pnt.x > co->minx && pnt.x < co->maxx
			                    && pnt.y < co->maxy
			&& pnt.z > co->minz && pnt.z < co->maxz );
}

//////////////////////////////////////////////////////////////////////////
//
// Synopsis:
//		MStatus	collide( hairSystem, follicleIndex, hairPositions,
//						hairPositionsLast, hairWidths, startIndex,
//						endIndex, curTime, friction, privateData )
//
// Description:
//		This callback is invoked by Maya to test if a collision exists be-
//	tween the follicle (defined by `hairPositions', `hairPositionsLast'
//	and `hairWidths') and the collision objects associated with the
//	hairSystem. If a collision is detected, this routine should adjust
//	`hairPositions' to compensate. The `hairPositionsLast' can also be
//	modified to adjust the velocity, but this should only be a dampening
//	effect as the hair solver expects collisions to be dampened,
//
//		This method is invoked often (actually its once per hair times the
//	hairSystem shape's iterations factor). Thus with 10,000 follicles it
//	would be called 80,000 times per frame (Note: as of Maya 7.0, the
//	iterations factor is multipled by 2, so at its default value of 4, you
//	get 8x calls. However, if you set iterations=0, it clamps to 1x calls).
//
// Parameters:
//		MObject hairSystem	: (in)	The hair system shape node.
//		int		follicleIndex:(in)	Which follicle we are processing.
//									You can get the follicle node if you
//									wish via MHairSystem::getFollicle().
//		MVectorArray &hairPositions:
//							  (mod)	Array of locations in world space
//									where the hair system is trying to
//									move the follicle. The first entry
//									corresponds to the root of the hair
//									and the last entry to the tip. If a
//									collision is detected, these values
//									should be updated appropriately.
//									Note that hairPositions can change
//									from iteration to iteration on the
//									same hair and same frame. You can set
//									a position, and then find the hair has
//									moved a bit the next iteration. There
//									are two reasons for this phenomenom:
//									  1) Other collisions could occur,
//										 including self collision.
//									  2) Stiffness is actually applied PER
//										 ITERATION.
//		MVectorArray &hairPositionsLast:
//							  (mod)	Array of the position at the previous
//									time for each entry in `hairPositions'.
//		MDoubleArray &hairWidths:
//							  (in)	Array of widths, representing the
//									width of the follcle at each entry in
//									`hairPositions'.
//		int		startIndex	: (in)	First index in `hairPositions' we
//									can move. This will be 0 unless the
//									root is locked in which case it will
//									be 1.
//		int		endIndex	: (in)	Last index in `hairPositions' we can
//									move. Will be the full array (N-1)
//									unless the tip is locked.
//		double	curTime		: (in)	Start of current time interval.
//		double	friction	: (in)	Frictional coefficient.
//		void	*privateData: (in)	If a privateData record was returned
//									from preFrame() it will be passed in
//									here. This is an optimisation to save
//									expensive lookups of your private data
//									if stored on the node.
//
// Returns:
//		bool	true		: Successfully performed collision testing and
//							  adjustment of the hair array data.
//		bool	false		: An error occurred.
//
//////////////////////////////////////////////////////////////////////////


#define	EPSILON	0.0001

bool	collide(
				const MObject		hairSystem,
				const int			follicleIndex,
				MVectorArray		&hairPositions,
				MVectorArray		&hairPositionsLast,
				const MDoubleArray	&hairWidths,
				const int			startIndex,
				const int			endIndex,
				const double		curTime,
				const double		friction,
				const void			*privateData )
{
	// Get the private data for the collision objects which was returned
	// from preFrame().
	//
	COLLISION_INFO *ci = (COLLISION_INFO *) privateData;
	if ( !ci ) {
		fprintf( stderr,"%s:%d: collide() privateData pointer is NULL\n",
				__FILE__, __LINE__ );
		return( false );
	}

	// If object has no vertices, or hair has no segments, then there is
	// nothing to collide. In our example, we'll return here, but if you
	// want to implement your own hair processing such as smoothing or
	// freeze on the hair, you could proceed and let the processing happen
	// after the object loop so that the data gets processed even if no
	// collisions occur.
	//
	if ( ci->numObjs <= 0 || hairPositions.length() <= 0 ) {
		return( true );
	}

	int		obj;
	for ( obj = 0; obj < ci->numObjs; ++obj ) {
		COLLISION_OBJ *co = &ci->objs[obj];

		// For our plug-in example, we just collide the segments of the
		// hair against the top of the bounding box for the geometry.
		// In an implementation where you only care about hair falling
		// downwards onto flat objects, this might be OK. However, in the
		// most deluxe of implementation, you should do the following:
		//
		//		o  Determine the motion of each face of your collision
		//		   object during the time range.
		//		o  Step through the follicle, and consider each segment
		//		   to be a moving cylinder where the start and end radii
		//		   can differ. The radii come from `hairWidths' and the
		//		   motion comes from the difference between `hairPositions'
		//		   and `hairPositionsLast'.
		//		o  Intersect each moving element (e.g. moving triangle
		//		   if you have a mesh obect) with each hair segment
		//		   e.g. moving cylinder). This intersection may occur
		//		   at a point within the frame. (Remember that the
		//		   hairPositions[] array holds the DESIRED location where
		//		   the hair system wants the hair to go.
		//		o  There can be multiple collisions along a hair segment.
		//		   Use the first location found and the maximal velocity.
		//
		// If a collision is detected, the `hairPositions' array should be
		// updated. `hairPositionsLast' may also be updated to provide a
		// dampening effect only.
		//

		// Loop through the follicle, starting at the root and advancing
		// toward the tip.
		//
		int		numSegments = hairPositions.length() - 1;
		int		seg;
		for ( seg = 0; seg < numSegments; ++seg ) {
			// Get the desired position of the segment (i.e. where the
			// solver is trying to put the hair for the current frame)
			// and the velocity required to get to that desired position.
			//
			// Thus,
			//		P = hairPositions		// Desired pos'n at cur frame.
			//		L = hairPositionsLast	// Position at prev frame.
			//		V = P - L				// Desired velocity of hair.
			//
			MVector pStart = hairPositions[seg];
			MVector pEnd = hairPositions[seg + 1];

			MVector vStart = pStart - hairPositionsLast[seg];
			MVector vEnd = pEnd - hairPositionsLast[seg + 1];

			// The proper way to time sample is to intersect the moving
			// segment of width `hairWidths[seg] to hairWidths[seg + 1]'
			// with the moving collision object. For the purposes of our
			// demo plug-in, we will simply assume the collision object is
			// static, the hair segment has zero width, and instead of
			// intersecting continuously in time, we will sample discrete
			// steps along the segment.
			//
			#define STEPS 4
			int		step;
			for ( step = 0; step < STEPS; ++step ) {
				// Compute the point for this step and its corresponding
				// velocity. This is a "time swept" point:
				//	p1 = desired position at current time
				//	p0 = position at previous time to achieve desired pos
				//
				MVector pCur, pPrev, v;
				double fracAlongSeg = step / ( (double) STEPS );
				v = vStart * ( 1.0 - fracAlongSeg ) + vEnd * fracAlongSeg;
				MVector p1 = pStart * ( 1.0 - fracAlongSeg )
						+ pEnd * fracAlongSeg;
				MVector p0 = p1 - v;

				// See if BOTH endpoints are outside of the bounding box
				// on the same side. If so, then the segment cannot
				// intersect the bounding box. Note that we assume the
				// bounding box is static.
				//
				if (       (p0.x < co->minx && p1.x < co->minx)
						|| (p0.y < co->miny && p1.y < co->miny)
						|| (p0.z < co->minz && p1.z < co->minz)
						|| (p0.x > co->maxx && p1.x > co->maxx)
						|| (p0.y > co->maxy && p1.y > co->maxy)
						|| (p0.z > co->maxz && p1.z > co->maxz) ) {
					continue;
				}

				// For the purposes of this example plug-in, we'll assume
				// the hair always moves downwards (due to gravity and
				// thus in the negative Y direction). As such, we only
				// need to test for collisions with the TOP of the
				// bounding box. Expanding the example to all 6 sides is
				// left as an exercise for the reader.
				//
				// Remember that p1 is the point at current time, and
				// p0 is the point at the previous time. Since we assume
				// the bounding box to be static, this simplifies things.
				//
				MVector	where(-100000,-100000,-100000);	// Loc'n of collision
				double  fracTime;	// Time at which collision happens 0..1

				if ( fabs( v.y ) < EPSILON		// velocity is zero
						&& fabs( p1.y - co->maxy ) < EPSILON ) {	// right on the bbox
					// Velocity is zero and the desired location (p1) is
					// right on top of the bounding box.
					//
					where = p1;
					fracTime = 1.0;		// Collides right at end;
				} else {
					fracTime = ( co->maxy -  p0.y ) / v.y;
					if ( fracTime >= 0.0 && fracTime <= 1.0 ) {
						// Compute the collision of the swept point with the
						// plane defined by the top of the bounding box.
						//
						where = p0 + v * fracTime;
					}
				}

				// If `seg' lies between startIndex and endIndex
				// we can move it. If its <= startIndex, the root is
				// locked and if >= endIndex the tip is locked.
				//
				if ( seg >= startIndex && seg <= endIndex ) {
					// Since we are just colliding with the top of the
					// bounding box, the normal where we hit is (0,1,0).
					// For the object velocity, we SHOULD measure the
					// relative motion of the object during the time
					// interval, but for our example, we'll assume its
					// not moving (0,0,0).
					//
					bool segCollides = false;
					MVector normal(0.0,1.0,0.0);	// normal is always up
					MVector objectVel(0.0,0.0,0.0);	// assume bbox is static

					// If we get the this point, then the intersection
					// point `where' is on the plane of the bounding box
					// top. See if it lies within the actual bounds of the
					// boxtop, and if so compute the position and velocity
					// information and apply to the hair segment.
					//
					if ( where.x >= co->minx && where.x <= co->maxx
							&& where.z >= co->minz && where.z <= co->maxz
							&& fracTime >= 0.0 && fracTime <= 0.0 ) {
						// We have a collision at `where' with the plane.
						// Compute the new velocity for the hair at the
						// point of collision.
						//
						MVector objVelAlongNormal = (objectVel * normal)
								* normal;
						MVector objVelAlongTangent = objectVel
								- objVelAlongNormal;
						MVector pntVelAlongTangent = v - ( v * normal )
								* normal;
						MVector reflectedPntVelAlongTangent
								= pntVelAlongTangent * ( 1.0 - friction )
								+ objVelAlongTangent * friction;
						MVector newVel = objVelAlongNormal
								+ reflectedPntVelAlongTangent;

						// Update the hair position. It actually looks
						// more stable from a simulation standpoint to
						// just move the closest segment endpoint, but
						// you are free to experiment. What we'll do in
						// our example is move the closest segment end-
						// point by the amount the collided point (where)
						// has to move to have velocity `newVel' yet still
						// pass through `where' at time `fracTime'.
						//
						if ( fracAlongSeg > 0.5 ) {
							MVector	deltaPos = -newVel * fracTime;
							hairPositionsLast[seg + 1] += deltaPos;
							hairPositions[seg + 1] = hairPositionsLast[seg]
									+ newVel;
						} else {
							MVector	deltaPos = newVel * fracTime;
							hairPositionsLast[seg] += deltaPos;
							hairPositions[seg] = hairPositionsLast[seg]
									+ newVel;
						}
						segCollides = true;
					}

                    // Check for segment endpoints that may still be
					// inside. Note that segments which started out being
					// totally inside will never collide using the
					// algorithm we use above, so we'll simply clamp them
					// here. One might expect an inside-the-bounding-box
					// test instead. However, this does not work if the
					// collision object is a very thin object.
					//
					bool inside = false;
                    if ( belowCollisionPlane( co, hairPositions[seg] ) ) {
						hairPositions[seg].y = co->maxy + EPSILON;
						hairPositionsLast[seg] = hairPositions[seg] - objectVel;
						inside = true;
                    }
                    if ( belowCollisionPlane( co, hairPositions[seg+1] ) ) {
						hairPositions[seg+1].y = co->maxy + EPSILON;
						hairPositionsLast[seg+1] = hairPositions[seg+1] - objectVel;
						inside = true;
                    }

					// If we collided, go onto the next segment.
					//
					if ( segCollides || inside ) {
						goto nextSeg;
					}
				}
			}
nextSeg:;
    	}
	}

	// You could perform any global filtering that you want on the hair
	// right here. For example: smoothing. This code is independent of
	// whether or not a collision occurred, and note that collide() is
	// called even with zero collision objects, so you will be guaranteed
	// of reaching here once per hair per iteration.
	//

	return( true );
}

//////////////////////////////////////////////////////////////////////////
//
// Synopsis:
//		MStatus initializePlugin( MObject obj )
//
// Description:
//		Invoked upon plug-in load to register the plug-in and initialise.
//
// Parameters:
//		MObject obj			: (in)	Plug-in object being loaded.
//
// Returns:
//		MStatus::kSuccess	: Successfully performed any needed initial-
//							  isation for the plug-in.
//		MStatus statusCode	: Error was detected.
//
//////////////////////////////////////////////////////////////////////////


MStatus initializePlugin( MObject obj )
{
	MFnPlugin	plugin( obj, "Autodesk", "8.0", "Any" );

	CHECK_MSTATUS( MHairSystem::registerCollisionSolverCollide( collide ) );
	CHECK_MSTATUS( MHairSystem::registerCollisionSolverPreFrame( preFrame ) );

	return( MS::kSuccess );
}

//////////////////////////////////////////////////////////////////////////
//
// Synopsis:
//		MStatus uninitializePlugin( MObject obj )
//
// Description:
//		Invoked upon plug-in unload to deregister the plug-in and clean up.
//
// Parameters:
//		MObject obj			: (in)	Plug-in object being unloaded.
//
// Returns:
//		MStatus::kSuccess	: Successfully performed any needed cleanup.
//		MStatus statusCode	: Error was detected.
//
//////////////////////////////////////////////////////////////////////////


MStatus uninitializePlugin( MObject obj )
{
	MFnPlugin	plugin( obj );

	CHECK_MSTATUS( MHairSystem::unregisterCollisionSolverCollide() );
	CHECK_MSTATUS( MHairSystem::unregisterCollisionSolverPreFrame() );

	return( MS::kSuccess );
}
