|
World of Rigid Bodies (WoRB)
|
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 }