1 | /* |
2 | * Copyright (c) 2006-2011 Erin Catto http://www.box2d.org |
3 | * |
4 | * This software is provided 'as-is', without any express or implied |
5 | * warranty. In no event will the authors be held liable for any damages |
6 | * arising from the use of this software. |
7 | * Permission is granted to anyone to use this software for any purpose, |
8 | * including commercial applications, and to alter it and redistribute it |
9 | * freely, subject to the following restrictions: |
10 | * 1. The origin of this software must not be misrepresented; you must not |
11 | * claim that you wrote the original software. If you use this software |
12 | * in a product, an acknowledgment in the product documentation would be |
13 | * appreciated but is not required. |
14 | * 2. Altered source versions must be plainly marked as such, and must not be |
15 | * misrepresented as being the original software. |
16 | * 3. This notice may not be removed or altered from any source distribution. |
17 | */ |
18 | |
19 | #include <Box2D/Collision/b2Distance.h> |
20 | #include <Box2D/Dynamics/b2Island.h> |
21 | #include <Box2D/Dynamics/b2Body.h> |
22 | #include <Box2D/Dynamics/b2Fixture.h> |
23 | #include <Box2D/Dynamics/b2World.h> |
24 | #include <Box2D/Dynamics/Contacts/b2Contact.h> |
25 | #include <Box2D/Dynamics/Contacts/b2ContactSolver.h> |
26 | #include <Box2D/Dynamics/Joints/b2Joint.h> |
27 | #include <Box2D/Common/b2StackAllocator.h> |
28 | #include <Box2D/Common/b2Timer.h> |
29 | |
30 | /* |
31 | Position Correction Notes |
32 | ========================= |
33 | I tried the several algorithms for position correction of the 2D revolute joint. |
34 | I looked at these systems: |
35 | - simple pendulum (1m diameter sphere on massless 5m stick) with initial angular velocity of 100 rad/s. |
36 | - suspension bridge with 30 1m long planks of length 1m. |
37 | - multi-link chain with 30 1m long links. |
38 | |
39 | Here are the algorithms: |
40 | |
41 | Baumgarte - A fraction of the position error is added to the velocity error. There is no |
42 | separate position solver. |
43 | |
44 | Pseudo Velocities - After the velocity solver and position integration, |
45 | the position error, Jacobian, and effective mass are recomputed. Then |
46 | the velocity constraints are solved with pseudo velocities and a fraction |
47 | of the position error is added to the pseudo velocity error. The pseudo |
48 | velocities are initialized to zero and there is no warm-starting. After |
49 | the position solver, the pseudo velocities are added to the positions. |
50 | This is also called the First Order World method or the Position LCP method. |
51 | |
52 | Modified Nonlinear Gauss-Seidel (NGS) - Like Pseudo Velocities except the |
53 | position error is re-computed for each constraint and the positions are updated |
54 | after the constraint is solved. The radius vectors (aka Jacobians) are |
55 | re-computed too (otherwise the algorithm has horrible instability). The pseudo |
56 | velocity states are not needed because they are effectively zero at the beginning |
57 | of each iteration. Since we have the current position error, we allow the |
58 | iterations to terminate early if the error becomes smaller than b2_linearSlop. |
59 | |
60 | Full NGS or just NGS - Like Modified NGS except the effective mass are re-computed |
61 | each time a constraint is solved. |
62 | |
63 | Here are the results: |
64 | Baumgarte - this is the cheapest algorithm but it has some stability problems, |
65 | especially with the bridge. The chain links separate easily close to the root |
66 | and they jitter as they struggle to pull together. This is one of the most common |
67 | methods in the field. The big drawback is that the position correction artificially |
68 | affects the momentum, thus leading to instabilities and false bounce. I used a |
69 | bias factor of 0.2. A larger bias factor makes the bridge less stable, a smaller |
70 | factor makes joints and contacts more spongy. |
71 | |
72 | Pseudo Velocities - the is more stable than the Baumgarte method. The bridge is |
73 | stable. However, joints still separate with large angular velocities. Drag the |
74 | simple pendulum in a circle quickly and the joint will separate. The chain separates |
75 | easily and does not recover. I used a bias factor of 0.2. A larger value lead to |
76 | the bridge collapsing when a heavy cube drops on it. |
77 | |
78 | Modified NGS - this algorithm is better in some ways than Baumgarte and Pseudo |
79 | Velocities, but in other ways it is worse. The bridge and chain are much more |
80 | stable, but the simple pendulum goes unstable at high angular velocities. |
81 | |
82 | Full NGS - stable in all tests. The joints display good stiffness. The bridge |
83 | still sags, but this is better than infinite forces. |
84 | |
85 | Recommendations |
86 | Pseudo Velocities are not really worthwhile because the bridge and chain cannot |
87 | recover from joint separation. In other cases the benefit over Baumgarte is small. |
88 | |
89 | Modified NGS is not a robust method for the revolute joint due to the violent |
90 | instability seen in the simple pendulum. Perhaps it is viable with other constraint |
91 | types, especially scalar constraints where the effective mass is a scalar. |
92 | |
93 | This leaves Baumgarte and Full NGS. Baumgarte has small, but manageable instabilities |
94 | and is very fast. I don't think we can escape Baumgarte, especially in highly |
95 | demanding cases where high constraint fidelity is not needed. |
96 | |
97 | Full NGS is robust and easy on the eyes. I recommend this as an option for |
98 | higher fidelity simulation and certainly for suspension bridges and long chains. |
99 | Full NGS might be a good choice for ragdolls, especially motorized ragdolls where |
100 | joint separation can be problematic. The number of NGS iterations can be reduced |
101 | for better performance without harming robustness much. |
102 | |
103 | Each joint in a can be handled differently in the position solver. So I recommend |
104 | a system where the user can select the algorithm on a per joint basis. I would |
105 | probably default to the slower Full NGS and let the user select the faster |
106 | Baumgarte method in performance critical scenarios. |
107 | */ |
108 | |
109 | /* |
110 | Cache Performance |
111 | |
112 | The Box2D solvers are dominated by cache misses. Data structures are designed |
113 | to increase the number of cache hits. Much of misses are due to random access |
114 | to body data. The constraint structures are iterated over linearly, which leads |
115 | to few cache misses. |
116 | |
117 | The bodies are not accessed during iteration. Instead read only data, such as |
118 | the mass values are stored with the constraints. The mutable data are the constraint |
119 | impulses and the bodies velocities/positions. The impulses are held inside the |
120 | constraint structures. The body velocities/positions are held in compact, temporary |
121 | arrays to increase the number of cache hits. Linear and angular velocity are |
122 | stored in a single array since multiple arrays lead to multiple misses. |
123 | */ |
124 | |
125 | /* |
126 | 2D Rotation |
127 | |
128 | R = [cos(theta) -sin(theta)] |
129 | [sin(theta) cos(theta) ] |
130 | |
131 | thetaDot = omega |
132 | |
133 | Let q1 = cos(theta), q2 = sin(theta). |
134 | R = [q1 -q2] |
135 | [q2 q1] |
136 | |
137 | q1Dot = -thetaDot * q2 |
138 | q2Dot = thetaDot * q1 |
139 | |
140 | q1_new = q1_old - dt * w * q2 |
141 | q2_new = q2_old + dt * w * q1 |
142 | then normalize. |
143 | |
144 | This might be faster than computing sin+cos. |
145 | However, we can compute sin+cos of the same angle fast. |
146 | */ |
147 | |
148 | b2Island::b2Island( |
149 | int32 bodyCapacity, |
150 | int32 contactCapacity, |
151 | int32 jointCapacity, |
152 | b2StackAllocator* allocator, |
153 | b2ContactListener* listener) |
154 | { |
155 | m_bodyCapacity = bodyCapacity; |
156 | m_contactCapacity = contactCapacity; |
157 | m_jointCapacity = jointCapacity; |
158 | m_bodyCount = 0; |
159 | m_contactCount = 0; |
160 | m_jointCount = 0; |
161 | |
162 | m_allocator = allocator; |
163 | m_listener = listener; |
164 | |
165 | m_bodies = (b2Body**)m_allocator->Allocate(bodyCapacity * sizeof(b2Body*)); |
166 | m_contacts = (b2Contact**)m_allocator->Allocate(contactCapacity * sizeof(b2Contact*)); |
167 | m_joints = (b2Joint**)m_allocator->Allocate(jointCapacity * sizeof(b2Joint*)); |
168 | |
169 | m_velocities = (b2Velocity*)m_allocator->Allocate(m_bodyCapacity * sizeof(b2Velocity)); |
170 | m_positions = (b2Position*)m_allocator->Allocate(m_bodyCapacity * sizeof(b2Position)); |
171 | } |
172 | |
173 | b2Island::~b2Island() |
174 | { |
175 | // Warning: the order should reverse the constructor order. |
176 | m_allocator->Free(m_positions); |
177 | m_allocator->Free(m_velocities); |
178 | m_allocator->Free(m_joints); |
179 | m_allocator->Free(m_contacts); |
180 | m_allocator->Free(m_bodies); |
181 | } |
182 | |
183 | void b2Island::Solve(b2Profile* profile, const b2TimeStep& step, const b2Vec2& gravity, bool allowSleep) |
184 | { |
185 | b2Timer timer; |
186 | |
187 | float32 h = step.dt; |
188 | |
189 | // Integrate velocities and apply damping. Initialize the body state. |
190 | for (int32 i = 0; i < m_bodyCount; ++i) |
191 | { |
192 | b2Body* b = m_bodies[i]; |
193 | |
194 | b2Vec2 c = b->m_sweep.c; |
195 | float32 a = b->m_sweep.a; |
196 | b2Vec2 v = b->m_linearVelocity; |
197 | float32 w = b->m_angularVelocity; |
198 | |
199 | // Store positions for continuous collision. |
200 | b->m_sweep.c0 = b->m_sweep.c; |
201 | b->m_sweep.a0 = b->m_sweep.a; |
202 | |
203 | if (b->m_type == b2_dynamicBody) |
204 | { |
205 | // Integrate velocities. |
206 | v += h * (b->m_gravityScale * gravity + b->m_invMass * b->m_force); |
207 | w += h * b->m_invI * b->m_torque; |
208 | |
209 | // Apply damping. |
210 | // ODE: dv/dt + c * v = 0 |
211 | // Solution: v(t) = v0 * exp(-c * t) |
212 | // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) |
213 | // v2 = exp(-c * dt) * v1 |
214 | // Pade approximation: |
215 | // v2 = v1 * 1 / (1 + c * dt) |
216 | v *= 1.0f / (1.0f + h * b->m_linearDamping); |
217 | w *= 1.0f / (1.0f + h * b->m_angularDamping); |
218 | } |
219 | |
220 | m_positions[i].c = c; |
221 | m_positions[i].a = a; |
222 | m_velocities[i].v = v; |
223 | m_velocities[i].w = w; |
224 | } |
225 | |
226 | timer.Reset(); |
227 | |
228 | // Solver data |
229 | b2SolverData solverData; |
230 | solverData.step = step; |
231 | solverData.positions = m_positions; |
232 | solverData.velocities = m_velocities; |
233 | |
234 | // Initialize velocity constraints. |
235 | b2ContactSolverDef contactSolverDef; |
236 | contactSolverDef.step = step; |
237 | contactSolverDef.contacts = m_contacts; |
238 | contactSolverDef.count = m_contactCount; |
239 | contactSolverDef.positions = m_positions; |
240 | contactSolverDef.velocities = m_velocities; |
241 | contactSolverDef.allocator = m_allocator; |
242 | |
243 | b2ContactSolver contactSolver(&contactSolverDef); |
244 | contactSolver.InitializeVelocityConstraints(); |
245 | |
246 | if (step.warmStarting) |
247 | { |
248 | contactSolver.WarmStart(); |
249 | } |
250 | |
251 | for (int32 i = 0; i < m_jointCount; ++i) |
252 | { |
253 | m_joints[i]->InitVelocityConstraints(solverData); |
254 | } |
255 | |
256 | profile->solveInit = timer.GetMilliseconds(); |
257 | |
258 | // Solve velocity constraints |
259 | timer.Reset(); |
260 | for (int32 i = 0; i < step.velocityIterations; ++i) |
261 | { |
262 | for (int32 j = 0; j < m_jointCount; ++j) |
263 | { |
264 | m_joints[j]->SolveVelocityConstraints(solverData); |
265 | } |
266 | |
267 | contactSolver.SolveVelocityConstraints(); |
268 | } |
269 | |
270 | // Store impulses for warm starting |
271 | contactSolver.StoreImpulses(); |
272 | profile->solveVelocity = timer.GetMilliseconds(); |
273 | |
274 | // Integrate positions |
275 | for (int32 i = 0; i < m_bodyCount; ++i) |
276 | { |
277 | b2Vec2 c = m_positions[i].c; |
278 | float32 a = m_positions[i].a; |
279 | b2Vec2 v = m_velocities[i].v; |
280 | float32 w = m_velocities[i].w; |
281 | |
282 | // Check for large velocities |
283 | b2Vec2 translation = h * v; |
284 | if (b2Dot(translation, translation) > b2_maxTranslationSquared) |
285 | { |
286 | float32 ratio = b2_maxTranslation / translation.Length(); |
287 | v *= ratio; |
288 | } |
289 | |
290 | float32 rotation = h * w; |
291 | if (rotation * rotation > b2_maxRotationSquared) |
292 | { |
293 | float32 ratio = b2_maxRotation / b2Abs(rotation); |
294 | w *= ratio; |
295 | } |
296 | |
297 | // Integrate |
298 | c += h * v; |
299 | a += h * w; |
300 | |
301 | m_positions[i].c = c; |
302 | m_positions[i].a = a; |
303 | m_velocities[i].v = v; |
304 | m_velocities[i].w = w; |
305 | } |
306 | |
307 | // Solve position constraints |
308 | timer.Reset(); |
309 | bool positionSolved = false; |
310 | for (int32 i = 0; i < step.positionIterations; ++i) |
311 | { |
312 | bool contactsOkay = contactSolver.SolvePositionConstraints(); |
313 | |
314 | bool jointsOkay = true; |
315 | for (int32 i = 0; i < m_jointCount; ++i) |
316 | { |
317 | bool jointOkay = m_joints[i]->SolvePositionConstraints(solverData); |
318 | jointsOkay = jointsOkay && jointOkay; |
319 | } |
320 | |
321 | if (contactsOkay && jointsOkay) |
322 | { |
323 | // Exit early if the position errors are small. |
324 | positionSolved = true; |
325 | break; |
326 | } |
327 | } |
328 | |
329 | // Copy state buffers back to the bodies |
330 | for (int32 i = 0; i < m_bodyCount; ++i) |
331 | { |
332 | b2Body* body = m_bodies[i]; |
333 | body->m_sweep.c = m_positions[i].c; |
334 | body->m_sweep.a = m_positions[i].a; |
335 | body->m_linearVelocity = m_velocities[i].v; |
336 | body->m_angularVelocity = m_velocities[i].w; |
337 | body->SynchronizeTransform(); |
338 | } |
339 | |
340 | profile->solvePosition = timer.GetMilliseconds(); |
341 | |
342 | Report(contactSolver.m_velocityConstraints); |
343 | |
344 | if (allowSleep) |
345 | { |
346 | float32 minSleepTime = b2_maxFloat; |
347 | |
348 | const float32 linTolSqr = b2_linearSleepTolerance * b2_linearSleepTolerance; |
349 | const float32 angTolSqr = b2_angularSleepTolerance * b2_angularSleepTolerance; |
350 | |
351 | for (int32 i = 0; i < m_bodyCount; ++i) |
352 | { |
353 | b2Body* b = m_bodies[i]; |
354 | if (b->GetType() == b2_staticBody) |
355 | { |
356 | continue; |
357 | } |
358 | |
359 | if ((b->m_flags & b2Body::e_autoSleepFlag) == 0 || |
360 | b->m_angularVelocity * b->m_angularVelocity > angTolSqr || |
361 | b2Dot(b->m_linearVelocity, b->m_linearVelocity) > linTolSqr) |
362 | { |
363 | b->m_sleepTime = 0.0f; |
364 | minSleepTime = 0.0f; |
365 | } |
366 | else |
367 | { |
368 | b->m_sleepTime += h; |
369 | minSleepTime = b2Min(minSleepTime, b->m_sleepTime); |
370 | } |
371 | } |
372 | |
373 | if (minSleepTime >= b2_timeToSleep && positionSolved) |
374 | { |
375 | for (int32 i = 0; i < m_bodyCount; ++i) |
376 | { |
377 | b2Body* b = m_bodies[i]; |
378 | b->SetAwake(false); |
379 | } |
380 | } |
381 | } |
382 | } |
383 | |
384 | void b2Island::SolveTOI(const b2TimeStep& subStep, int32 toiIndexA, int32 toiIndexB) |
385 | { |
386 | b2Assert(toiIndexA < m_bodyCount); |
387 | b2Assert(toiIndexB < m_bodyCount); |
388 | |
389 | // Initialize the body state. |
390 | for (int32 i = 0; i < m_bodyCount; ++i) |
391 | { |
392 | b2Body* b = m_bodies[i]; |
393 | m_positions[i].c = b->m_sweep.c; |
394 | m_positions[i].a = b->m_sweep.a; |
395 | m_velocities[i].v = b->m_linearVelocity; |
396 | m_velocities[i].w = b->m_angularVelocity; |
397 | } |
398 | |
399 | b2ContactSolverDef contactSolverDef; |
400 | contactSolverDef.contacts = m_contacts; |
401 | contactSolverDef.count = m_contactCount; |
402 | contactSolverDef.allocator = m_allocator; |
403 | contactSolverDef.step = subStep; |
404 | contactSolverDef.positions = m_positions; |
405 | contactSolverDef.velocities = m_velocities; |
406 | b2ContactSolver contactSolver(&contactSolverDef); |
407 | |
408 | // Solve position constraints. |
409 | for (int32 i = 0; i < subStep.positionIterations; ++i) |
410 | { |
411 | bool contactsOkay = contactSolver.SolveTOIPositionConstraints(toiIndexA, toiIndexB); |
412 | if (contactsOkay) |
413 | { |
414 | break; |
415 | } |
416 | } |
417 | |
418 | #if 0 |
419 | // Is the new position really safe? |
420 | for (int32 i = 0; i < m_contactCount; ++i) |
421 | { |
422 | b2Contact* c = m_contacts[i]; |
423 | b2Fixture* fA = c->GetFixtureA(); |
424 | b2Fixture* fB = c->GetFixtureB(); |
425 | |
426 | b2Body* bA = fA->GetBody(); |
427 | b2Body* bB = fB->GetBody(); |
428 | |
429 | int32 indexA = c->GetChildIndexA(); |
430 | int32 indexB = c->GetChildIndexB(); |
431 | |
432 | b2DistanceInput input; |
433 | input.proxyA.Set(fA->GetShape(), indexA); |
434 | input.proxyB.Set(fB->GetShape(), indexB); |
435 | input.transformA = bA->GetTransform(); |
436 | input.transformB = bB->GetTransform(); |
437 | input.useRadii = false; |
438 | |
439 | b2DistanceOutput output; |
440 | b2SimplexCache cache; |
441 | cache.count = 0; |
442 | b2Distance(&output, &cache, &input); |
443 | |
444 | if (output.distance == 0 || cache.count == 3) |
445 | { |
446 | cache.count += 0; |
447 | } |
448 | } |
449 | #endif |
450 | |
451 | // Leap of faith to new safe state. |
452 | m_bodies[toiIndexA]->m_sweep.c0 = m_positions[toiIndexA].c; |
453 | m_bodies[toiIndexA]->m_sweep.a0 = m_positions[toiIndexA].a; |
454 | m_bodies[toiIndexB]->m_sweep.c0 = m_positions[toiIndexB].c; |
455 | m_bodies[toiIndexB]->m_sweep.a0 = m_positions[toiIndexB].a; |
456 | |
457 | // No warm starting is needed for TOI events because warm |
458 | // starting impulses were applied in the discrete solver. |
459 | contactSolver.InitializeVelocityConstraints(); |
460 | |
461 | // Solve velocity constraints. |
462 | for (int32 i = 0; i < subStep.velocityIterations; ++i) |
463 | { |
464 | contactSolver.SolveVelocityConstraints(); |
465 | } |
466 | |
467 | // Don't store the TOI contact forces for warm starting |
468 | // because they can be quite large. |
469 | |
470 | float32 h = subStep.dt; |
471 | |
472 | // Integrate positions |
473 | for (int32 i = 0; i < m_bodyCount; ++i) |
474 | { |
475 | b2Vec2 c = m_positions[i].c; |
476 | float32 a = m_positions[i].a; |
477 | b2Vec2 v = m_velocities[i].v; |
478 | float32 w = m_velocities[i].w; |
479 | |
480 | // Check for large velocities |
481 | b2Vec2 translation = h * v; |
482 | if (b2Dot(translation, translation) > b2_maxTranslationSquared) |
483 | { |
484 | float32 ratio = b2_maxTranslation / translation.Length(); |
485 | v *= ratio; |
486 | } |
487 | |
488 | float32 rotation = h * w; |
489 | if (rotation * rotation > b2_maxRotationSquared) |
490 | { |
491 | float32 ratio = b2_maxRotation / b2Abs(rotation); |
492 | w *= ratio; |
493 | } |
494 | |
495 | // Integrate |
496 | c += h * v; |
497 | a += h * w; |
498 | |
499 | m_positions[i].c = c; |
500 | m_positions[i].a = a; |
501 | m_velocities[i].v = v; |
502 | m_velocities[i].w = w; |
503 | |
504 | // Sync bodies |
505 | b2Body* body = m_bodies[i]; |
506 | body->m_sweep.c = c; |
507 | body->m_sweep.a = a; |
508 | body->m_linearVelocity = v; |
509 | body->m_angularVelocity = w; |
510 | body->SynchronizeTransform(); |
511 | } |
512 | |
513 | Report(contactSolver.m_velocityConstraints); |
514 | } |
515 | |
516 | void b2Island::Report(const b2ContactVelocityConstraint* constraints) |
517 | { |
518 | if (m_listener == NULL) |
519 | { |
520 | return; |
521 | } |
522 | |
523 | for (int32 i = 0; i < m_contactCount; ++i) |
524 | { |
525 | b2Contact* c = m_contacts[i]; |
526 | |
527 | const b2ContactVelocityConstraint* vc = constraints + i; |
528 | |
529 | b2ContactImpulse impulse; |
530 | impulse.count = vc->pointCount; |
531 | for (int32 j = 0; j < vc->pointCount; ++j) |
532 | { |
533 | impulse.normalImpulses[j] = vc->points[j].normalImpulse; |
534 | impulse.tangentImpulses[j] = vc->points[j].tangentImpulse; |
535 | } |
536 | |
537 | m_listener->PostSolve(c, &impulse); |
538 | } |
539 | } |
540 | |