File indexing completed on 2025-08-03 03:50:01
0001 /* 0002 * Copyright (c) 2006-2009 Erin Catto http://www.gphysics.com 0003 * 0004 * This software is provided 'as-is', without any express or implied 0005 * warranty. In no event will the authors be held liable for any damages 0006 * arising from the use of this software. 0007 * Permission is granted to anyone to use this software for any purpose, 0008 * including commercial applications, and to alter it and redistribute it 0009 * freely, subject to the following restrictions: 0010 * 1. The origin of this software must not be misrepresented; you must not 0011 * claim that you wrote the original software. If you use this software 0012 * in a product, an acknowledgment in the product documentation would be 0013 * appreciated but is not required. 0014 * 2. Altered source versions must be plainly marked as such, and must not be 0015 * misrepresented as being the original software. 0016 * 3. This notice may not be removed or altered from any source distribution. 0017 */ 0018 0019 #include <Box2D/Collision/b2Distance.h> 0020 #include <Box2D/Dynamics/b2Island.h> 0021 #include <Box2D/Dynamics/b2Fixture.h> 0022 #include <Box2D/Dynamics/b2World.h> 0023 #include <Box2D/Dynamics/Contacts/b2Contact.h> 0024 #include <Box2D/Dynamics/Contacts/b2ContactSolver.h> 0025 #include <Box2D/Dynamics/Joints/b2Joint.h> 0026 #include <Box2D/Common/b2StackAllocator.h> 0027 0028 /* 0029 Position Correction Notes 0030 ========================= 0031 I tried the several algorithms for position correction of the 2D revolute joint. 0032 I looked at these systems: 0033 - simple pendulum (1m diameter sphere on massless 5m stick) with initial angular velocity of 100 rad/s. 0034 - suspension bridge with 30 1m long planks of length 1m. 0035 - multi-link chain with 30 1m long links. 0036 0037 Here are the algorithms: 0038 0039 Baumgarte - A fraction of the position error is added to the velocity error. There is no 0040 separate position solver. 0041 0042 Pseudo Velocities - After the velocity solver and position integration, 0043 the position error, Jacobian, and effective mass are recomputed. Then 0044 the velocity constraints are solved with pseudo velocities and a fraction 0045 of the position error is added to the pseudo velocity error. The pseudo 0046 velocities are initialized to zero and there is no warm-starting. After 0047 the position solver, the pseudo velocities are added to the positions. 0048 This is also called the First Order World method or the Position LCP method. 0049 0050 Modified Nonlinear Gauss-Seidel (NGS) - Like Pseudo Velocities except the 0051 position error is re-computed for each constraint and the positions are updated 0052 after the constraint is solved. The radius vectors (aka Jacobians) are 0053 re-computed too (otherwise the algorithm has horrible instability). The pseudo 0054 velocity states are not needed because they are effectively zero at the beginning 0055 of each iteration. Since we have the current position error, we allow the 0056 iterations to terminate early if the error becomes smaller than b2_linearSlop. 0057 0058 Full NGS or just NGS - Like Modified NGS except the effective mass are re-computed 0059 each time a constraint is solved. 0060 0061 Here are the results: 0062 Baumgarte - this is the cheapest algorithm but it has some stability problems, 0063 especially with the bridge. The chain links separate easily close to the root 0064 and they jitter as they struggle to pull together. This is one of the most common 0065 methods in the field. The big drawback is that the position correction artificially 0066 affects the momentum, thus leading to instabilities and false bounce. I used a 0067 bias factor of 0.2. A larger bias factor makes the bridge less stable, a smaller 0068 factor makes joints and contacts more spongy. 0069 0070 Pseudo Velocities - the is more stable than the Baumgarte method. The bridge is 0071 stable. However, joints still separate with large angular velocities. Drag the 0072 simple pendulum in a circle quickly and the joint will separate. The chain separates 0073 easily and does not recover. I used a bias factor of 0.2. A larger value lead to 0074 the bridge collapsing when a heavy cube drops on it. 0075 0076 Modified NGS - this algorithm is better in some ways than Baumgarte and Pseudo 0077 Velocities, but in other ways it is worse. The bridge and chain are much more 0078 stable, but the simple pendulum goes unstable at high angular velocities. 0079 0080 Full NGS - stable in all tests. The joints display good stiffness. The bridge 0081 still sags, but this is better than infinite forces. 0082 0083 Recommendations 0084 Pseudo Velocities are not really worthwhile because the bridge and chain cannot 0085 recover from joint separation. In other cases the benefit over Baumgarte is small. 0086 0087 Modified NGS is not a robust method for the revolute joint due to the violent 0088 instability seen in the simple pendulum. Perhaps it is viable with other constraint 0089 types, especially scalar constraints where the effective mass is a scalar. 0090 0091 This leaves Baumgarte and Full NGS. Baumgarte has small, but manageable instabilities 0092 and is very fast. I don't think we can escape Baumgarte, especially in highly 0093 demanding cases where high constraint fidelity is not needed. 0094 0095 Full NGS is robust and easy on the eyes. I recommend this as an option for 0096 higher fidelity simulation and certainly for suspension bridges and long chains. 0097 Full NGS might be a good choice for ragdolls, especially motorized ragdolls where 0098 joint separation can be problematic. The number of NGS iterations can be reduced 0099 for better performance without harming robustness much. 0100 0101 Each joint in a can be handled differently in the position solver. So I recommend 0102 a system where the user can select the algorithm on a per joint basis. I would 0103 probably default to the slower Full NGS and let the user select the faster 0104 Baumgarte method in performance critical scenarios. 0105 */ 0106 0107 /* 0108 Cache Performance 0109 0110 The Box2D solvers are dominated by cache misses. Data structures are designed 0111 to increase the number of cache hits. Much of misses are due to random access 0112 to body data. The constraint structures are iterated over linearly, which leads 0113 to few cache misses. 0114 0115 The bodies are not accessed during iteration. Instead read only data, such as 0116 the mass values are stored with the constraints. The mutable data are the constraint 0117 impulses and the bodies velocities/positions. The impulses are held inside the 0118 constraint structures. The body velocities/positions are held in compact, temporary 0119 arrays to increase the number of cache hits. Linear and angular velocity are 0120 stored in a single array since multiple arrays lead to multiple misses. 0121 */ 0122 0123 /* 0124 2D Rotation 0125 0126 R = [cos(theta) -sin(theta)] 0127 [sin(theta) cos(theta) ] 0128 0129 thetaDot = omega 0130 0131 Let q1 = cos(theta), q2 = sin(theta). 0132 R = [q1 -q2] 0133 [q2 q1] 0134 0135 q1Dot = -thetaDot * q2 0136 q2Dot = thetaDot * q1 0137 0138 q1_new = q1_old - dt * w * q2 0139 q2_new = q2_old + dt * w * q1 0140 then normalize. 0141 0142 This might be faster than computing sin+cos. 0143 However, we can compute sin+cos of the same angle fast. 0144 */ 0145 0146 b2Island::b2Island( 0147 int32 bodyCapacity, 0148 int32 contactCapacity, 0149 int32 jointCapacity, 0150 b2StackAllocator* allocator, 0151 b2ContactListener* listener) 0152 { 0153 m_bodyCapacity = bodyCapacity; 0154 m_contactCapacity = contactCapacity; 0155 m_jointCapacity = jointCapacity; 0156 m_bodyCount = 0; 0157 m_contactCount = 0; 0158 m_jointCount = 0; 0159 0160 m_allocator = allocator; 0161 m_listener = listener; 0162 0163 m_bodies = (b2Body**)m_allocator->Allocate(bodyCapacity * sizeof(b2Body*)); 0164 m_contacts = (b2Contact**)m_allocator->Allocate(contactCapacity * sizeof(b2Contact*)); 0165 m_joints = (b2Joint**)m_allocator->Allocate(jointCapacity * sizeof(b2Joint*)); 0166 0167 m_velocities = (b2Velocity*)m_allocator->Allocate(m_bodyCapacity * sizeof(b2Velocity)); 0168 m_positions = (b2Position*)m_allocator->Allocate(m_bodyCapacity * sizeof(b2Position)); 0169 } 0170 0171 b2Island::~b2Island() 0172 { 0173 // Warning: the order should reverse the constructor order. 0174 m_allocator->Free(m_positions); 0175 m_allocator->Free(m_velocities); 0176 m_allocator->Free(m_joints); 0177 m_allocator->Free(m_contacts); 0178 m_allocator->Free(m_bodies); 0179 } 0180 0181 void b2Island::Solve(const b2TimeStep& step, const b2Vec2& gravity, bool allowSleep) 0182 { 0183 // Integrate velocities and apply damping. 0184 for (int32 i = 0; i < m_bodyCount; ++i) 0185 { 0186 b2Body* b = m_bodies[i]; 0187 0188 if (b->GetType() != b2_dynamicBody) 0189 { 0190 continue; 0191 } 0192 0193 // Integrate velocities. 0194 b->m_linearVelocity += step.dt * (gravity + b->m_invMass * b->m_force); 0195 b->m_angularVelocity += step.dt * b->m_invI * b->m_torque; 0196 0197 // Apply damping. 0198 // ODE: dv/dt + c * v = 0 0199 // Solution: v(t) = v0 * exp(-c * t) 0200 // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) 0201 // v2 = exp(-c * dt) * v1 0202 // Taylor expansion: 0203 // v2 = (1.0f - c * dt) * v1 0204 b->m_linearVelocity *= b2Clamp(1.0f - step.dt * b->m_linearDamping, 0.0f, 1.0f); 0205 b->m_angularVelocity *= b2Clamp(1.0f - step.dt * b->m_angularDamping, 0.0f, 1.0f); 0206 } 0207 0208 // Partition contacts so that contacts with static bodies are solved last. 0209 int32 i1 = -1; 0210 for (int32 i2 = 0; i2 < m_contactCount; ++i2) 0211 { 0212 b2Fixture* fixtureA = m_contacts[i2]->GetFixtureA(); 0213 b2Fixture* fixtureB = m_contacts[i2]->GetFixtureB(); 0214 b2Body* bodyA = fixtureA->GetBody(); 0215 b2Body* bodyB = fixtureB->GetBody(); 0216 bool nonStatic = bodyA->GetType() != b2_staticBody && bodyB->GetType() != b2_staticBody; 0217 if (nonStatic) 0218 { 0219 ++i1; 0220 b2Swap(m_contacts[i1], m_contacts[i2]); 0221 } 0222 } 0223 0224 // Initialize velocity constraints. 0225 b2ContactSolverDef solverDef; 0226 solverDef.contacts = m_contacts; 0227 solverDef.count = m_contactCount; 0228 solverDef.allocator = m_allocator; 0229 solverDef.impulseRatio = step.dtRatio; 0230 solverDef.warmStarting = step.warmStarting; 0231 0232 b2ContactSolver contactSolver(&solverDef); 0233 0234 contactSolver.InitializeVelocityConstraints(); 0235 0236 if (step.warmStarting) 0237 { 0238 contactSolver.WarmStart(); 0239 } 0240 0241 for (int32 i = 0; i < m_jointCount; ++i) 0242 { 0243 m_joints[i]->InitVelocityConstraints(step); 0244 } 0245 0246 // Solve velocity constraints. 0247 for (int32 i = 0; i < step.velocityIterations; ++i) 0248 { 0249 for (int32 j = 0; j < m_jointCount; ++j) 0250 { 0251 m_joints[j]->SolveVelocityConstraints(step); 0252 } 0253 0254 contactSolver.SolveVelocityConstraints(); 0255 } 0256 0257 // Post-solve (store impulses for warm starting). 0258 contactSolver.StoreImpulses(); 0259 0260 // Integrate positions. 0261 for (int32 i = 0; i < m_bodyCount; ++i) 0262 { 0263 b2Body* b = m_bodies[i]; 0264 0265 if (b->GetType() == b2_staticBody) 0266 { 0267 continue; 0268 } 0269 0270 // Check for large velocities. 0271 b2Vec2 translation = step.dt * b->m_linearVelocity; 0272 if (b2Dot(translation, translation) > b2_maxTranslationSquared) 0273 { 0274 qreal ratio = b2_maxTranslation / translation.Length(); 0275 b->m_linearVelocity *= ratio; 0276 } 0277 0278 qreal rotation = step.dt * b->m_angularVelocity; 0279 if (rotation * rotation > b2_maxRotationSquared) 0280 { 0281 qreal ratio = b2_maxRotation / b2Abs(rotation); 0282 b->m_angularVelocity *= ratio; 0283 } 0284 0285 // Store positions for continuous collision. 0286 b->m_sweep.c0 = b->m_sweep.c; 0287 b->m_sweep.a0 = b->m_sweep.a; 0288 0289 // Integrate 0290 b->m_sweep.c += step.dt * b->m_linearVelocity; 0291 b->m_sweep.a += step.dt * b->m_angularVelocity; 0292 0293 // Compute new transform 0294 b->SynchronizeTransform(); 0295 0296 // Note: shapes are synchronized later. 0297 } 0298 0299 // Iterate over constraints. 0300 for (int32 i = 0; i < step.positionIterations; ++i) 0301 { 0302 bool contactsOkay = contactSolver.SolvePositionConstraints(b2_contactBaumgarte); 0303 0304 bool jointsOkay = true; 0305 for (int32 i = 0; i < m_jointCount; ++i) 0306 { 0307 bool jointOkay = m_joints[i]->SolvePositionConstraints(b2_contactBaumgarte); 0308 jointsOkay = jointsOkay && jointOkay; 0309 } 0310 0311 if (contactsOkay && jointsOkay) 0312 { 0313 // Exit early if the position errors are small. 0314 break; 0315 } 0316 } 0317 0318 Report(contactSolver.m_constraints); 0319 0320 if (allowSleep) 0321 { 0322 qreal minSleepTime = b2_maxFloat; 0323 0324 const qreal linTolSqr = b2_linearSleepTolerance * b2_linearSleepTolerance; 0325 const qreal angTolSqr = b2_angularSleepTolerance * b2_angularSleepTolerance; 0326 0327 for (int32 i = 0; i < m_bodyCount; ++i) 0328 { 0329 b2Body* b = m_bodies[i]; 0330 if (b->GetType() == b2_staticBody) 0331 { 0332 continue; 0333 } 0334 0335 if ((b->m_flags & b2Body::e_autoSleepFlag) == 0) 0336 { 0337 b->m_sleepTime = 0.0f; 0338 minSleepTime = 0.0f; 0339 } 0340 0341 if ((b->m_flags & b2Body::e_autoSleepFlag) == 0 || 0342 b->m_angularVelocity * b->m_angularVelocity > angTolSqr || 0343 b2Dot(b->m_linearVelocity, b->m_linearVelocity) > linTolSqr) 0344 { 0345 b->m_sleepTime = 0.0f; 0346 minSleepTime = 0.0f; 0347 } 0348 else 0349 { 0350 b->m_sleepTime += step.dt; 0351 minSleepTime = b2Min(minSleepTime, b->m_sleepTime); 0352 } 0353 } 0354 0355 if (minSleepTime >= b2_timeToSleep) 0356 { 0357 for (int32 i = 0; i < m_bodyCount; ++i) 0358 { 0359 b2Body* b = m_bodies[i]; 0360 b->SetAwake(false); 0361 } 0362 } 0363 } 0364 } 0365 0366 void b2Island::SolveTOI(const b2TimeStep& subStep, const b2Body* bodyA, const b2Body* bodyB) 0367 { 0368 b2ContactSolverDef solverDef; 0369 solverDef.contacts = m_contacts; 0370 solverDef.count = m_contactCount; 0371 solverDef.allocator = m_allocator; 0372 solverDef.impulseRatio = subStep.dtRatio; 0373 solverDef.warmStarting = subStep.warmStarting; 0374 b2ContactSolver contactSolver(&solverDef); 0375 0376 // Solve position constraints. 0377 const qreal k_toiBaumgarte = 0.75f; 0378 for (int32 i = 0; i < subStep.positionIterations; ++i) 0379 { 0380 bool contactsOkay = contactSolver.SolveTOIPositionConstraints(k_toiBaumgarte, bodyA, bodyB); 0381 if (contactsOkay) 0382 { 0383 break; 0384 } 0385 0386 if (i == subStep.positionIterations - 1) 0387 { 0388 i += 0; 0389 } 0390 } 0391 0392 #if 0 0393 // Is the new position really safe? 0394 for (int32 i = 0; i < m_contactCount; ++i) 0395 { 0396 b2Contact* c = m_contacts[i]; 0397 b2Fixture* fA = c->GetFixtureA(); 0398 b2Fixture* fB = c->GetFixtureB(); 0399 0400 b2Body* bA = fA->GetBody(); 0401 b2Body* bB = fB->GetBody(); 0402 0403 int32 indexA = c->GetChildIndexA(); 0404 int32 indexB = c->GetChildIndexB(); 0405 0406 b2DistanceInput input; 0407 input.proxyA.Set(fA->GetShape(), indexA); 0408 input.proxyB.Set(fB->GetShape(), indexB); 0409 input.transformA = bA->GetTransform(); 0410 input.transformB = bB->GetTransform(); 0411 input.useRadii = false; 0412 0413 b2DistanceOutput output; 0414 b2SimplexCache cache; 0415 cache.count = 0; 0416 b2Distance(&output, &cache, &input); 0417 0418 if (output.distance == 0 || cache.count == 3) 0419 { 0420 cache.count += 0; 0421 } 0422 } 0423 #endif 0424 0425 // Leap of faith to new safe state. 0426 for (int32 i = 0; i < m_bodyCount; ++i) 0427 { 0428 m_bodies[i]->m_sweep.a0 = m_bodies[i]->m_sweep.a; 0429 m_bodies[i]->m_sweep.c0 = m_bodies[i]->m_sweep.c; 0430 } 0431 0432 // No warm starting is needed for TOI events because warm 0433 // starting impulses were applied in the discrete solver. 0434 contactSolver.InitializeVelocityConstraints(); 0435 0436 // Solve velocity constraints. 0437 for (int32 i = 0; i < subStep.velocityIterations; ++i) 0438 { 0439 contactSolver.SolveVelocityConstraints(); 0440 } 0441 0442 // Don't store the TOI contact forces for warm starting 0443 // because they can be quite large. 0444 0445 // Integrate positions. 0446 for (int32 i = 0; i < m_bodyCount; ++i) 0447 { 0448 b2Body* b = m_bodies[i]; 0449 0450 if (b->GetType() == b2_staticBody) 0451 { 0452 continue; 0453 } 0454 0455 // Check for large velocities. 0456 b2Vec2 translation = subStep.dt * b->m_linearVelocity; 0457 if (b2Dot(translation, translation) > b2_maxTranslationSquared) 0458 { 0459 translation.Normalize(); 0460 b->m_linearVelocity = (b2_maxTranslation * subStep.inv_dt) * translation; 0461 } 0462 0463 qreal rotation = subStep.dt * b->m_angularVelocity; 0464 if (rotation * rotation > b2_maxRotationSquared) 0465 { 0466 if (rotation < 0.0) 0467 { 0468 b->m_angularVelocity = -subStep.inv_dt * b2_maxRotation; 0469 } 0470 else 0471 { 0472 b->m_angularVelocity = subStep.inv_dt * b2_maxRotation; 0473 } 0474 } 0475 0476 // Integrate 0477 b->m_sweep.c += subStep.dt * b->m_linearVelocity; 0478 b->m_sweep.a += subStep.dt * b->m_angularVelocity; 0479 0480 // Compute new transform 0481 b->SynchronizeTransform(); 0482 0483 // Note: shapes are synchronized later. 0484 } 0485 0486 Report(contactSolver.m_constraints); 0487 } 0488 0489 void b2Island::Report(const b2ContactConstraint* constraints) 0490 { 0491 if (m_listener == NULL) 0492 { 0493 return; 0494 } 0495 0496 for (int32 i = 0; i < m_contactCount; ++i) 0497 { 0498 b2Contact* c = m_contacts[i]; 0499 0500 const b2ContactConstraint* cc = constraints + i; 0501 0502 b2ContactImpulse impulse; 0503 for (int32 j = 0; j < cc->pointCount; ++j) 0504 { 0505 impulse.normalImpulses[j] = cc->points[j].normalImpulse; 0506 impulse.tangentImpulses[j] = cc->points[j].tangentImpulse; 0507 } 0508 0509 m_listener->PostSolve(c, &impulse); 0510 } 0511 }