World of Rigid Bodies (WoRB)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines
ImpulseMethod.cpp
Go to the documentation of this file.
00001 /**
00002  *  @file      ImpulseMethod.cpp
00003  *  @brief     Implementation of the impulse transfer method algorithm.
00004  *  @author    Mikica Kocic
00005  *  @version   0.18
00006  *  @date      2012-05-02
00007  *  @copyright GNU Public License.
00008  */
00009 
00010 #include "WoRB.h"
00011 
00012 using namespace WoRB;
00013 
00014 /////////////////////////////////////////////////////////////////////////////////////////
00015 // Resolves collisions in the system using the impulse transfer method.
00016 //
00017 void CollisionResolver::ImpulseTransfers( double h, unsigned maxIterations, double eps )
00018 {
00019     if ( CollisionCount == 0 ) {
00020         return; // Nothing to do
00021     }
00022 
00023     // Setup default parameters
00024     //
00025     if ( maxIterations == 0 ) {
00026         maxIterations = 8 * CollisionCount;
00027     }
00028     if ( eps == 0 ) {
00029         eps = 0.01;
00030     }
00031 
00032     // Iterate performing impulse transfers, until there are no contacts with
00033     // notable bouncing velocity jolts are found.
00034     //
00035     for ( unsigned iteration = 0; iteration < maxIterations; ++iteration )
00036     {
00037         // Find the contact with the largest possible velocity jolt
00038         //
00039         Collision* contact = FindLargestBouncingVelocity( eps );
00040         if ( ! contact ) {
00041             break; // Done, if bouncing velocity are not found
00042         }
00043 
00044         // Activate bodies participating in the collision that are lying inactive
00045         //
00046         contact->ActivateInactiveBodies ();
00047 
00048         // Calculate velocity and angular velocity jolts
00049         //
00050         Quaternion V_jolt[2], W_jolt[2];
00051         contact->ImpulseTransfer( V_jolt, W_jolt );
00052 
00053         // With the change in velocity of the two bodies, the update of
00054         // contact velocities means that some of the relative closing
00055         // velocities need recomputing.
00056         //
00057         RigidBody** bodies_in_contact = &contact->Body_A;
00058         for( unsigned i = 0; i < CollisionCount; ++i )
00059         {
00060             Collision& c_i = Collisions[i];
00061             RigidBody** b_i = &c_i.Body_A;
00062 
00063             for( unsigned a = 0; a < 2; ++a ) // Each body in contact
00064             {
00065                 if ( ! b_i[a] ) { 
00066                     continue; // Skip scenery objects
00067                 }
00068 
00069                 // Check for a match with each body in the newly resolved contact
00070                 //
00071                 for( unsigned b = 0; b < 2; ++b )
00072                 {
00073                     if ( b_i[a] != bodies_in_contact[b] ) {
00074                         continue;
00075                     }
00076 
00077                     // dV = V_j + ( W_j x r )
00078                     //
00079                     Quaternion delta_V = 
00080                         V_jolt[b] + W_jolt[b].Cross( c_i.RelativePosition[a] );
00081 
00082                     // The sign of the change is negative if we're dealing
00083                     // with the second body in a contact.
00084                     //
00085                     Quaternion dV_world = c_i.ToWorld.TransformInverse( delta_V );
00086                     c_i.Velocity += a ? -dV_world : dV_world;
00087 
00088                     // Recalculate bouncing velocity (derived quantity).
00089                     // BouncingVelocity = - ( 1 + COR ) * Velocity.x
00090                     // where Velocity.x = < V_ab, Normal_ab >
00091                     //
00092                     c_i.BouncingVelocity = c_i.GetBouncingVelocity( h );
00093                 }
00094             }
00095         }
00096     }
00097 }
00098 
00099 /////////////////////////////////////////////////////////////////////////////////////////
00100 // Impulse transfer for the single collision
00101 //
00102 void Collision::ImpulseTransfer(
00103         Quaternion V_jolt[2], // calculated linear velocity change
00104         Quaternion W_jolt[2]  // calculated angular velocity change
00105     )
00106 {
00107     // Get the collision impulse in contact frame of reference
00108     //
00109     Quaternion J_contact = Friction == 0
00110         ? GetImpulse ()  // Use the simplifed version in case of frictionless contacts
00111         : GetImpulse_IncludeFriction ();  // The fuull version including friction
00112 
00113     // Convert impulse to world space, then split it into linear and angular components
00114     //
00115     Quaternion J        = ToWorld( J_contact );
00116     Quaternion J_torque = RelativePosition[0].Cross( J );
00117 
00118     // Apply the jolts on the first body
00119     //
00120     Body_A->LinearMomentum  += J;
00121     Body_A->AngularMomentum += J_torque;
00122 
00123     V_jolt[0] = Body_A->InverseMass * J;
00124     W_jolt[0] = Body_A->InverseInertiaWorld * J_torque;
00125 
00126     // Apply the jolts on the second body, if it's not a scenery
00127     //
00128     if ( Body_B ) 
00129     {
00130         J_torque = RelativePosition[1].Cross( J );
00131 
00132         Body_B->LinearMomentum  -= J;
00133         Body_B->AngularMomentum -= J_torque;
00134 
00135         V_jolt[1] = -( Body_B->InverseMass * J );
00136         W_jolt[1] = -( Body_B->InverseInertiaWorld * J_torque );
00137     }
00138 
00139     // Note: The linear and the angular velocity will be derived later
00140     // in RigidBody::CalculateDerivedQuantities() method.
00141 }
00142 
00143 Quaternion Collision::GetImpulse ()
00144 {
00145     // Build a vector that shows the change in velocity in world space for 
00146     // a unit impulse in the direction of the contact normal, then calculate the change 
00147     // in velocity in contact coordiantes, adding also the linear component of 
00148     // velocity jolt (body inverse mass 1/M).
00149     //
00150     // Equation (8-18) in 
00151     // Baraff, David - Physically Based Modeling, Rigid Body Simulation
00152     //
00153     //                 - ( 1 + COR ) * Velocity.x                     Bouncing velocity
00154     // j =  ------------------------------------------------------ = -------------------
00155     //       Sum( m^-1 + [ ( I_world^-1 • ( r × N ) ) x r ] • N )     Inv. reduced mass
00156     //
00157 
00158     // First, calculate:
00159     // iMass = Sum( m^-1 + ( ( I_world^-1 • ( r × N ) ) x r ) • N ) for each body
00160     //
00161     double invRedMass = 0;
00162     
00163     invRedMass += Body_A->InverseMass;
00164     invRedMass += ( Body_A->InverseInertiaWorld 
00165                     * ( RelativePosition[0].Cross( Normal ) )
00166                   ).Cross( RelativePosition[0] ).Dot( Normal );
00167 
00168     if ( Body_B ) {
00169         invRedMass += Body_B->InverseMass;
00170         invRedMass += ( Body_B->InverseInertiaWorld 
00171                         * ( RelativePosition[1].Cross( Normal ) )
00172                       ).Cross( RelativePosition[1] ).Dot( Normal );
00173     }
00174 
00175     // Finally, return the correct sized impulse in the contact frame of reference
00176     // along the contact normal (= X-component). 
00177     //
00178     return SpatialVector( BouncingVelocity / invRedMass, 0, 0 );
00179 }
00180 
00181 Quaternion Collision::GetImpulse_IncludeFriction ()
00182 {
00183     // Build the matrix for converting between linear and angular quantities.
00184     // (Multiplying by a skew symmetrix matrix is equivalent to a cross product.)
00185     //
00186     QTensor crossR;
00187     crossR.SetSkewSymmetric( RelativePosition[0] );
00188 
00189     // Build the matrix to convert contact impulse to change in velocity
00190     // in world coordinates.
00191     //
00192     // dV_world = - ( r × I_world^-1 ) × r
00193     //
00194     QTensor delta_V_world = -( crossR * Body_A->InverseInertiaWorld * crossR );
00195 
00196     // Do the same for the second body
00197     //
00198     if ( Body_B )
00199     {
00200         // Calculate the velocity jolt matrix for body B and sum with the velocity
00201         // jolt for body A
00202         //
00203         // dV_world = - ( r_a × I_a_world^-1 ) × r_a - ( r_b × I_b_world^-1 ) × r_b
00204         //
00205         crossR.SetSkewSymmetric( RelativePosition[1] ); // cross product matrix
00206         delta_V_world += -( crossR * Body_B->InverseInertiaWorld * crossR );
00207     }
00208 
00209     // Do a change of basis to convert into contact coordinates.
00210     //
00211     QTensor delta_V_contact = ToWorld.TransformInverse( delta_V_world );
00212 
00213     // Include the linear effects of the inverse reduced mass m_ab^-1
00214     //
00215     // m_ab^-1 = m_a^-1 + m_b^-1
00216     //
00217     double inverseReducedMass = 
00218         Body_A->InverseMass + ( Body_B ? Body_B->InverseMass : 0.0 );
00219 
00220     delta_V_contact.m.xx += inverseReducedMass;
00221     delta_V_contact.m.yy += inverseReducedMass;
00222     delta_V_contact.m.zz += inverseReducedMass;
00223 
00224     if ( Friction == 0 ) {
00225         return Quaternion( 0, BouncingVelocity / delta_V_contact.m.xx, 0, 0 );
00226     }
00227 
00228     // For static friction we should just remove tangential components.
00229     //
00230     #if 0
00231         return Quaternion( 0,
00232             BouncingVelocity / delta_V_contact.m.xx, 
00233             -Velocity.y / delta_V_contact.m.yy * Friction, 
00234             -Velocity.z / delta_V_contact.m.zz * Friction
00235         );
00236     #endif
00237 
00238     // Find the target normal & tangential velocities to remove
00239     //
00240     Quaternion target_V( 0, BouncingVelocity, -Velocity.y, -Velocity.z );
00241 
00242     // Find the impulse to remove target velocities in contact frame of reference
00243     // (multiplying by dV^-1 we get the impulse needed per unit velocity)
00244     //
00245     // dV = m_a^-1 + m_b^-1 
00246     //    + ToContact • [                           <-- ToContact == ToWorld^-1
00247     //            - ( r_a × I_a_world^-1 ) × r_a
00248     //            - ( r_b × I_b_world^-1 ) × r_b
00249     //      ]
00250     //
00251     // J = dV^-1 • { -( 1 + COR ) * V.x, - V.y, - V.z }
00252     //
00253     Quaternion J = delta_V_contact.Inverse () * target_V;
00254     
00255     // Use dynamic friction in case of an exceeding friction.
00256     //
00257     double J_tangential = sqrt( J.y * J.y + J.z * J.z ); // tangential component
00258 
00259     if ( J_tangential > J.x * Friction ) // if excceding friction
00260     {
00261         // Normalize tangential components
00262         J.y /= J_tangential;
00263         J.z /= J_tangential;
00264 
00265         // Recalculate impuls
00266         double invm = delta_V_contact.m.xx  
00267                     + delta_V_contact.m.xy * Friction * J.y 
00268                     + delta_V_contact.m.xz * Friction * J.z;
00269         double j_normal = BouncingVelocity / invm;
00270 
00271         // Reduce friction for the given amount.
00272         J.x = j_normal;
00273         J.y *= Friction * j_normal;
00274         J.z *= Friction * j_normal;
00275     }
00276 
00277     return J;
00278 }