//- // ========================================================================== // 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 ); }
Autodesk® Maya® 2009 © 1997-2008 Autodesk, Inc. All rights reserved. | Generated with 1.5.6 |