- Main Page
- Related Pages
- Modules
- Classes
- Examples

# hairCollisionSolver.cpp

    //-
    // ==========================================================================
    // 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 );
    }