ContactSolver.cpp
Go to the documentation of this file.
1 /*
2  * Original work Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
3  * Modified work Copyright (c) 2017 Louis Langholtz https://github.com/louis-langholtz/PlayRho
4  *
5  * This software is provided 'as-is', without any express or implied
6  * warranty. In no event will the authors be held liable for any damages
7  * arising from the use of this software.
8  * Permission is granted to anyone to use this software for any purpose,
9  * including commercial applications, and to alter it and redistribute it
10  * freely, subject to the following restrictions:
11  * 1. The origin of this software must not be misrepresented; you must not
12  * claim that you wrote the original software. If you use this software
13  * in a product, an acknowledgment in the product documentation would be
14  * appreciated but is not required.
15  * 2. Altered source versions must be plainly marked as such, and must not be
16  * misrepresented as being the original software.
17  * 3. This notice may not be removed or altered from any source distribution.
18  */
19 
28 
29 #include <algorithm>
30 
31 #if !defined(NDEBUG)
32 // Solver debugging is normally disabled because the block solver sometimes has to deal with a
33 // poorly conditioned effective mass matrix.
34 //#define PLAYRHO_DEBUG_SOLVER 1
35 #endif
36 
37 namespace playrho {
38 namespace d2 {
39 namespace {
40 
41 #if defined(B2_DEBUG_SOLVER)
42 static PLAYRHO_CONSTEXPR inline auto k_errorTol = 1e-3_mps;
43 #endif
44 
58 struct ImpulseChange
59 {
61  UnitVec direction;
62 };
63 
64 VelocityPair GetVelocityDelta(const VelocityConstraint& vc, const Momentum2 impulses)
65 {
66  assert(IsValid(impulses));
67 
68  const auto bodyA = vc.GetBodyA();
69  const auto bodyB = vc.GetBodyB();
70  const auto normal = vc.GetNormal();
71 
72  const auto invRotInertiaA = bodyA->GetInvRotInertia();
73  const auto invMassA = bodyA->GetInvMass();
74 
75  const auto invRotInertiaB = bodyB->GetInvRotInertia();
76  const auto invMassB = bodyB->GetInvMass();
77 
78  // Apply incremental impulse
79  const auto P0 = impulses[0] * normal;
80  const auto P1 = impulses[1] * normal;
81  const auto P = P0 + P1;
82  const auto LA = AngularMomentum{
83  (Cross(GetPointRelPosA(vc, 0), P0) + Cross(GetPointRelPosA(vc, 1), P1)) / Radian
84  };
85  const auto LB = AngularMomentum{
86  (Cross(GetPointRelPosB(vc, 0), P0) + Cross(GetPointRelPosB(vc, 1), P1)) / Radian
87  };
88  return VelocityPair{
89  -Velocity{invMassA * P, invRotInertiaA * LA},
90  +Velocity{invMassB * P, invRotInertiaB * LB}
91  };
92 }
93 
94 Momentum BlockSolveUpdate(VelocityConstraint& vc, const Momentum2 newImpulses)
95 {
96  const auto delta_v = GetVelocityDelta(vc, newImpulses - GetNormalImpulses(vc));
97  vc.GetBodyA()->SetVelocity(vc.GetBodyA()->GetVelocity() + std::get<0>(delta_v));
98  vc.GetBodyB()->SetVelocity(vc.GetBodyB()->GetVelocity() + std::get<1>(delta_v));
99  SetNormalImpulses(vc, newImpulses);
100  return std::max(abs(newImpulses[0]), abs(newImpulses[1]));
101 }
102 
103 Optional<Momentum> BlockSolveNormalCase1(VelocityConstraint& vc,
104  const LinearVelocity2 b_prime)
105 {
106  //
107  // Case 1: vn = 0
108  //
109  // 0 = A * x + b'
110  //
111  // Solve for x:
112  //
113  // x = -inv(A) * b'
114  //
115  const auto normalMass = vc.GetNormalMass();
116  const auto newImpulses = -Transform(b_prime, normalMass);
117  if ((newImpulses[0] >= 0_Ns) && (newImpulses[1] >= 0_Ns))
118  {
119  const auto max = BlockSolveUpdate(vc, newImpulses);
120 
121 #if defined(B2_DEBUG_SOLVER)
122  auto& vcp0 = vc.GetPointAt(0);
123  auto& vcp1 = vc.GetPointAt(1);
124  const auto velA = vc.GetBodyA()->GetVelocity();
125  const auto velB = vc.GetBodyB()->GetVelocity();
126 
127  // Postconditions
128  const auto dv0 = GetContactRelVelocity(velA, vcp0.relA, velB, vcp0.relB);
129  const auto dv1 = GetContactRelVelocity(velA, vcp1.relA, velB, vcp1.relB);
130 
131  // Compute normal velocity
132  const auto vn0 = Dot(dv0, vc.GetNormal());
133  const auto vn1 = Dot(dv1, vc.GetNormal());
134 
135  assert(abs(vn0 - vcp0.velocityBias) < k_errorTol);
136  assert(abs(vn1 - vcp1.velocityBias) < k_errorTol);
137 #endif
138  return Optional<Momentum>{max};
139  }
140  return Optional<Momentum>{};
141 }
142 
143 Optional<Momentum> BlockSolveNormalCase2(VelocityConstraint& vc, const LinearVelocity2 b_prime)
144 {
145  //
146  // Case 2: vn1 = 0 and x2 = 0
147  //
148  // 0 = a11 * x1 + a12 * 0 + b1'
149  // vn2 = a21 * x1 + a22 * 0 + b2'
150  //
151  const auto newImpulses = Momentum2{
152  -GetNormalMassAtPoint(vc, 0) * get<0>(b_prime), 0_Ns
153  };
154  const auto K = vc.GetK();
155  const auto vn2 = get<1>(get<0>(K)) * get<0>(newImpulses) + get<1>(b_prime);
156  if ((get<0>(newImpulses) >= 0_Ns) && (vn2 >= 0_mps))
157  {
158  const auto max = BlockSolveUpdate(vc, newImpulses);
159 
160 #if defined(B2_DEBUG_SOLVER)
161  auto& vcp1 = vc.GetPointAt(0);
162  const auto velA = vc.GetBodyA()->GetVelocity();
163  const auto velB = vc.GetBodyB()->GetVelocity();
164 
165  // Postconditions
166  const auto dv1 = GetContactRelVelocity(velA, vcp1.relA, velB, vcp1.relB);
167 
168  // Compute normal velocity
169  const auto vn1 = Dot(dv1, vc.GetNormal());
170 
171  assert(abs(vn1 - vcp1.velocityBias) < k_errorTol);
172 #endif
173  return Optional<Momentum>{max};
174  }
175  return Optional<Momentum>{};
176 }
177 
178 Optional<Momentum> BlockSolveNormalCase3(VelocityConstraint& vc, const LinearVelocity2 b_prime)
179 {
180  //
181  // Case 3: vn2 = 0 and x1 = 0
182  //
183  // vn1 = a11 * 0 + a12 * x2 + b1'
184  // 0 = a21 * 0 + a22 * x2 + b2'
185  //
186  const auto newImpulses = Momentum2{
187  0_Ns, -GetNormalMassAtPoint(vc, 1) * get<1>(b_prime)
188  };
189  const auto K = vc.GetK();
190  const auto vn1 = get<0>(get<1>(K)) * get<1>(newImpulses) + get<0>(b_prime);
191  if ((get<1>(newImpulses) >= 0_Ns) && (vn1 >= 0_mps))
192  {
193  const auto max = BlockSolveUpdate(vc, newImpulses);
194 
195 #if defined(B2_DEBUG_SOLVER)
196  auto& vcp2 = vc.GetPointAt(1);
197  const auto velA = vc.GetBodyA()->GetVelocity();
198  const auto velB = vc.GetBodyB()->GetVelocity();
199 
200  // Postconditions
201  const auto dv2 = GetContactRelVelocity(velA, vcp2.relA, velB, vcp2.relB);
202 
203  // Compute normal velocity
204  const auto vn2 = Dot(dv2, vc.GetNormal());
205 
206  assert(abs(vn2 - vcp2.velocityBias) < k_errorTol);
207 #endif
208  return Optional<Momentum>{max};
209  }
210  return Optional<Momentum>{};
211 }
212 
213 Optional<Momentum> BlockSolveNormalCase4(VelocityConstraint& vc, const LinearVelocity2 b_prime)
214 {
215  //
216  // Case 4: x1 = 0 and x2 = 0
217  //
218  // vn1 = b1
219  // vn2 = b2;
220  const auto vn1 = get<0>(b_prime);
221  const auto vn2 = get<1>(b_prime);
222  if ((vn1 >= 0_mps) && (vn2 >= 0_mps))
223  {
224  return Optional<Momentum>{BlockSolveUpdate(vc, Momentum2{0_Ns, 0_Ns})};
225  }
226  return Optional<Momentum>{};
227 }
228 
229 inline Momentum BlockSolveNormalConstraint(VelocityConstraint& vc)
230 {
231  assert(vc.GetPointCount() == 2);
232 
233  // Block solver developed in collaboration with Dirk Gregorius (back in 01/07 on Box2D_Lite).
234  // Build the mini LCP for this contact patch
235  //
236  // vn = A * x + b, vn >= 0, x >= 0 and vn_i * x_i = 0 with i = 1..2
237  //
238  // A = J * W * JT and J = ( -n, -r1 x n, n, r2 x n )
239  // b = vn0 - velocityBias
240  //
241  // The system is solved using the "Total enumeration method" (s. Murty). The complementary constraint vn_i * x_i
242  // implies that we must have in any solution either vn_i = 0 or x_i = 0. So for the 2-D contact problem the cases
243  // vn1 = 0 and vn2 = 0, x1 = 0 and x2 = 0, x1 = 0 and vn2 = 0, x2 = 0 and vn1 = 0 need to be tested. The first valid
244  // solution that satisfies the problem is chosen.
245  //
246  // In order to account of the accumulated impulse 'a' (because of the iterative nature of the solver which only requires
247  // that the accumulated impulse is clamped and not the incremental impulse) we change the impulse variable (x_i).
248  //
249  // Substitute:
250  //
251  // x = a + d
252  //
253  // a := old total impulse
254  // x := new total impulse
255  // d := incremental impulse
256  //
257  // For the current iteration we extend the formula for the incremental impulse
258  // to compute the new total impulse:
259  //
260  // vn = A * d + b
261  // = A * (x - a) + b
262  // = A * x + b - A * a
263  // = A * x + b'
264  // b' = b - A * a;
265 
266  const auto b_prime = [=]() {
267  const auto normal = vc.GetNormal();
268 
269  const auto velA = vc.GetBodyA()->GetVelocity();
270  const auto velB = vc.GetBodyB()->GetVelocity();
271  const auto ra0 = vc.GetPointRelPosA(0);
272  const auto rb0 = vc.GetPointRelPosB(0);
273  const auto ra1 = vc.GetPointRelPosA(1);
274  const auto rb1 = vc.GetPointRelPosB(1);
275 
276  const auto dv0 = GetContactRelVelocity(velA, ra0, velB, rb0);
277  const auto dv1 = GetContactRelVelocity(velA, ra1, velB, rb1);
278 
279  // Compute normal velocities
280  const auto vn0 = Dot(dv0, normal);
281  const auto vn1 = Dot(dv1, normal);
282 
283  // Compute b
284  const auto b = LinearVelocity2{
285  vn0 - vc.GetVelocityBiasAtPoint(0),
286  vn1 - vc.GetVelocityBiasAtPoint(1)
287  };
288 
289  // Return b'
290  const auto K = vc.GetK();
291  return b - Transform(GetNormalImpulses(vc), K);
292  }();
293 
294  auto maxIncImpulse = Optional<Momentum>{};
295  maxIncImpulse = BlockSolveNormalCase1(vc, b_prime);
296  if (maxIncImpulse.has_value())
297  {
298  return *maxIncImpulse;
299  }
300  maxIncImpulse = BlockSolveNormalCase2(vc, b_prime);
301  if (maxIncImpulse.has_value())
302  {
303  return *maxIncImpulse;
304  }
305  maxIncImpulse = BlockSolveNormalCase3(vc, b_prime);
306  if (maxIncImpulse.has_value())
307  {
308  return *maxIncImpulse;
309  }
310  maxIncImpulse = BlockSolveNormalCase4(vc, b_prime);
311  if (maxIncImpulse.has_value())
312  {
313  return *maxIncImpulse;
314  }
315 
316  // No solution, give up. This is hit sometimes, but it doesn't seem to matter.
317  return 0;
318 }
319 
320 inline Momentum SeqSolveNormalConstraint(VelocityConstraint& vc)
321 {
322  auto maxIncImpulse = 0_Ns;
323 
324  const auto direction = vc.GetNormal();
325  const auto count = vc.GetPointCount();
326  const auto bodyA = vc.GetBodyA();
327  const auto bodyB = vc.GetBodyB();
328 
329  const auto invRotInertiaA = bodyA->GetInvRotInertia();
330  const auto invMassA = bodyA->GetInvMass();
331  const auto oldVelA = bodyA->GetVelocity();
332 
333  const auto invRotInertiaB = bodyB->GetInvRotInertia();
334  const auto invMassB = bodyB->GetInvMass();
335  const auto oldVelB = bodyB->GetVelocity();
336 
337  auto newVelA = oldVelA;
338  auto newVelB = oldVelB;
339 
340  auto solverProc = [&](VelocityConstraint::size_type index) {
341  const auto vcp = vc.GetPointAt(index);
342  const auto closingVel = GetContactRelVelocity(newVelA, vcp.relA, newVelB, vcp.relB);
343  const auto directionalVel = LinearVelocity{Dot(closingVel, direction)};
344  const auto lambda = Momentum{vcp.normalMass * (vcp.velocityBias - directionalVel)};
345  const auto oldImpulse = vcp.normalImpulse;
346  const auto newImpulse = std::max(oldImpulse + lambda, 0_Ns);
347 #if 0
348  // Note: using AlmostEqual here results in increased iteration counts and is slower.
349  const auto incImpulse = AlmostEqual(newImpulse, oldImpulse)? 0_Ns: newImpulse - oldImpulse;
350 #else
351  const auto incImpulse = newImpulse - oldImpulse;
352 #endif
353  const auto P = incImpulse * direction;
354  const auto LA = AngularMomentum{Cross(vcp.relA, P) / Radian};
355  const auto LB = AngularMomentum{Cross(vcp.relB, P) / Radian};
356  newVelA -= Velocity{invMassA * P, invRotInertiaA * LA};
357  newVelB += Velocity{invMassB * P, invRotInertiaB * LB};
358  maxIncImpulse = std::max(maxIncImpulse, abs(incImpulse));
359 
360  // Note: using newImpulse, instead of oldImpulse + incImpulse, results in
361  // iteration count increases for the World.TilesComesToRest unit test.
362  vc.SetNormalImpulseAtPoint(index, oldImpulse + incImpulse);
363  };
364 
365  if (count == 2)
366  {
367  solverProc(1);
368  }
369  solverProc(0);
370 
371  bodyA->SetVelocity(newVelA);
372  bodyB->SetVelocity(newVelB);
373 
374  return maxIncImpulse;
375 }
376 
377 inline Momentum SolveTangentConstraint(VelocityConstraint& vc)
378 {
379  auto maxIncImpulse = 0_Ns;
380 
381  const auto direction = vc.GetTangent();
382  const auto friction = vc.GetFriction();
383  const auto tangentSpeed = vc.GetTangentSpeed();
384  const auto count = vc.GetPointCount();
385  const auto bodyA = vc.GetBodyA();
386  const auto bodyB = vc.GetBodyB();
387 
388  const auto invRotInertiaA = bodyA->GetInvRotInertia();
389  const auto invMassA = bodyA->GetInvMass();
390  const auto oldVelA = bodyA->GetVelocity();
391 
392  const auto invRotInertiaB = bodyB->GetInvRotInertia();
393  const auto invMassB = bodyB->GetInvMass();
394  const auto oldVelB = bodyB->GetVelocity();
395 
396  auto newVelA = oldVelA;
397  auto newVelB = oldVelB;
398 
399  auto solverProc = [&](VelocityConstraint::size_type index) {
400  const auto vcp = vc.GetPointAt(index);
401  const auto closingVel = GetContactRelVelocity(newVelA, vcp.relA, newVelB, vcp.relB);
402  const auto directionalVel = LinearVelocity{tangentSpeed - Dot(closingVel, direction)};
403  const auto lambda = vcp.tangentMass * directionalVel;
404  const auto maxImpulse = friction * vcp.normalImpulse;
405  const auto oldImpulse = vcp.tangentImpulse;
406  const auto newImpulse = std::clamp(oldImpulse + lambda, -maxImpulse, maxImpulse);
407 #if 0
408  // Note: using AlmostEqual here results in increased iteration counts and is slower.
409  const auto incImpulse = AlmostEqual(newImpulse, oldImpulse)? 0_Ns: newImpulse - oldImpulse;
410 #else
411  const auto incImpulse = newImpulse - oldImpulse;
412 #endif
413  const auto P = incImpulse * direction;
414  const auto LA = AngularMomentum{Cross(vcp.relA, P) / Radian};
415  const auto LB = AngularMomentum{Cross(vcp.relB, P) / Radian};
416  newVelA -= Velocity{invMassA * P, invRotInertiaA * LA};
417  newVelB += Velocity{invMassB * P, invRotInertiaB * LB};
418  maxIncImpulse = std::max(maxIncImpulse, abs(incImpulse));
419 
420  // Note: using newImpulse, instead of oldImpulse + incImpulse, results in
421  // iteration count increases for the World.TilesComesToRest unit test.
422  vc.SetTangentImpulseAtPoint(index, oldImpulse + incImpulse);
423  };
424  assert((count == 1) || (count == 2));
425  if (count == 2)
426  {
427  solverProc(1);
428  }
429  solverProc(0);
430 
431  bodyA->SetVelocity(newVelA);
432  bodyB->SetVelocity(newVelB);
433 
434  return maxIncImpulse;
435 }
436 
437 inline Momentum SolveNormalConstraint(VelocityConstraint& vc)
438 {
439 #if 1
440  // Note: Block solving reduces World.TilesComesToRest iteration counts and is faster.
441  // This is because the block solver provides more stable solutions per iteration.
442  // The difference is especially pronounced in the Vertical Stack Testbed demo.
443  const auto count = vc.GetPointCount();
444  assert((count == 1) || (count == 2));
445  if ((count == 1) || (vc.GetK() == InvMass22{}))
446  {
447  return SeqSolveNormalConstraint(vc);
448  }
449  return BlockSolveNormalConstraint(vc);
450 #else
451  return SeqSolveNormalConstraint(vc);
452 #endif
453 }
454 
455 }; // anonymous namespace
456 
457 } // namespace d2
458 
460 {
461  return ConstraintSolverConf{}
462  .UseResolutionRate(conf.regResolutionRate)
463  .UseLinearSlop(conf.linearSlop)
464  .UseAngularSlop(conf.angularSlop)
465  .UseMaxLinearCorrection(conf.maxLinearCorrection)
466  .UseMaxAngularCorrection(conf.maxAngularCorrection);
467 }
468 
470 {
471  return ConstraintSolverConf{}
472  .UseResolutionRate(conf.toiResolutionRate)
473  .UseLinearSlop(conf.linearSlop)
474  .UseAngularSlop(conf.angularSlop)
475  .UseMaxLinearCorrection(conf.maxLinearCorrection)
476  .UseMaxAngularCorrection(conf.maxAngularCorrection);
477 }
478 
479 namespace GaussSeidel {
480 
482 {
483  auto maxIncImpulse = 0_Ns;
484 
485  // Applies frictional changes to velocity.
486  maxIncImpulse = std::max(maxIncImpulse, d2::SolveTangentConstraint(vc));
487 
488  // Applies restitutional changes to velocity.
489  maxIncImpulse = std::max(maxIncImpulse, d2::SolveNormalConstraint(vc));
490 
491  return maxIncImpulse;
492 }
493 
495  const bool moveA, const bool moveB,
497 {
498  assert(IsValid(conf.resolutionRate));
499  assert(IsValid(conf.linearSlop));
500  assert(IsValid(conf.maxLinearCorrection));
501 
502  const auto bodyA = pc.GetBodyA();
503  const auto bodyB = pc.GetBodyB();
504 
505  const auto invMassA = moveA? bodyA->GetInvMass(): InvMass{0};
506  const auto invRotInertiaA = moveA? bodyA->GetInvRotInertia(): InvRotInertia{0};
507  const auto localCenterA = bodyA->GetLocalCenter();
508 
509  const auto invMassB = moveB? bodyB->GetInvMass(): InvMass{0};
510  const auto invRotInertiaB = moveB? bodyB->GetInvRotInertia(): InvRotInertia{0};
511  const auto localCenterB = bodyB->GetLocalCenter();
512 
513  // Compute inverse mass total.
514  // This must be > 0 unless doing TOI solving and neither bodies were the bodies specified.
515  const auto invMassTotal = invMassA + invMassB;
516  assert(invMassTotal >= InvMass{0});
517 
518  const auto totalRadius = pc.GetRadiusA() + pc.GetRadiusB();
519 
520  const auto solver_fn = [&](const d2::PositionSolverManifold psm,
521  const Length2 pA, const Length2 pB) {
522  const auto separation = psm.m_separation - totalRadius;
523  // Positive separation means shapes not overlapping and not touching.
524  // Zero separation means shapes are touching.
525  // Negative separation means shapes are overlapping.
526 
527  const auto rA = Length2{psm.m_point - pA};
528  const auto rB = Length2{psm.m_point - pB};
529 
530  // Compute the effective mass.
531  const auto K = InvMass{[&]() {
532  const auto rnA = Length{Cross(rA, psm.m_normal)} / Radian;
533  const auto rnB = Length{Cross(rB, psm.m_normal)} / Radian;
534  // InvRotInertia is L^-2 M^-1 QP^2
535  // L^-2 M^-1 QP^2 * L^2 is: M^-1 QP^2
536  const auto invRotMassA = InvMass{invRotInertiaA * Square(rnA)};
537  const auto invRotMassB = InvMass{invRotInertiaB * Square(rnB)};
538  return invMassTotal + invRotMassA + invRotMassB;
539  }()};
540 
541  assert(K >= InvMass{0});
542 
543  // Prevent large corrections & don't push separation above -conf.linearSlop.
544  const auto C = -std::clamp(conf.resolutionRate * (separation + conf.linearSlop),
545  -conf.maxLinearCorrection, 0_m);
546 
547  // Compute response factors...
548  const auto P = Length2{psm.m_normal * C} / K; // L M
549  const auto LA = Cross(rA, P) / Radian; // L^2 M QP^-1
550  const auto LB = Cross(rB, P) / Radian; // L^2 M QP^-1
551 
552  // InvMass is M^-1, and InvRotInertia is L^-2 M^-1 QP^2.
553  // Product of InvMass * P is: L
554  // Product of InvRotInertia * L{A,B} is: QP
555  return d2::PositionSolution{
556  -d2::Position{invMassA * P, invRotInertiaA * LA},
557  +d2::Position{invMassB * P, invRotInertiaB * LB},
558  separation
559  };
560  };
561 
562  auto posA = bodyA->GetPosition();
563  auto posB = bodyB->GetPosition();
564 
565  // Solve normal constraints
566  const auto pointCount = pc.manifold.GetPointCount();
567  switch (pointCount)
568  {
569  case 1:
570  {
571  const auto psm0 = GetPSM(pc.manifold, 0,
572  GetTransformation(posA, localCenterA),
573  GetTransformation(posB, localCenterB));
574  return d2::PositionSolution{posA, posB, 0} + solver_fn(psm0, posA.linear, posB.linear);
575  }
576  case 2:
577  {
578 #if 0
579  const auto psm0 = GetPSM(pc.manifold, 0,
580  GetTransformation(posA, localCenterA),
581  GetTransformation(posB, localCenterB));
582  const auto s0 = solver_fn(psm0, posA.linear, posB.linear);
583  posA += s0.pos_a;
584  posB += s0.pos_b;
585 
586  const auto psm1 = GetPSM(pc.manifold, 1,
587  GetTransformation(posA, localCenterA),
588  GetTransformation(posB, localCenterB));
589  const auto s1 = solver_fn(psm1, posA.linear, posB.linear);
590  posA += s1.pos_a;
591  posB += s1.pos_b;
592 
593  return PositionSolution{posA, posB, std::min(s0.min_separation, s1.min_separation)};
594 #else
595  const auto xfA = GetTransformation(posA, localCenterA);
596  const auto xfB = GetTransformation(posB, localCenterB);
597 
598  // solve most penatrating point first or solve simultaneously if about the same penetration
599  const auto psm0 = GetPSM(pc.manifold, 0, xfA, xfB);
600  const auto psm1 = GetPSM(pc.manifold, 1, xfA, xfB);
601 
602  assert(IsValid(psm0.m_separation) && IsValid(psm1.m_separation));
603 
604  if (AlmostEqual(StripUnit(psm0.m_separation), StripUnit(psm1.m_separation)))
605  {
606  const auto s0 = solver_fn(psm0, posA.linear, posB.linear);
607  const auto s1 = solver_fn(psm1, posA.linear, posB.linear);
608  //assert(s0.pos_a.angular == -s1.pos_a.angular);
609  //assert(s0.pos_b.angular == -s1.pos_b.angular);
610  return d2::PositionSolution{
611  posA + s0.pos_a + s1.pos_a,
612  posB + s0.pos_b + s1.pos_b,
613  s0.min_separation
614  };
615  }
616  if (psm0.m_separation < psm1.m_separation)
617  {
618  const auto s0 = solver_fn(psm0, posA.linear, posB.linear);
619  posA += s0.pos_a;
620  posB += s0.pos_b;
621  const auto psm1_prime = GetPSM(pc.manifold, 1,
622  GetTransformation(posA, localCenterA),
623  GetTransformation(posB, localCenterB));
624  const auto s1 = solver_fn(psm1_prime, posA.linear, posB.linear);
625  posA += s1.pos_a;
626  posB += s1.pos_b;
627  return d2::PositionSolution{posA, posB, s0.min_separation};
628  }
629  if (psm1.m_separation < psm0.m_separation)
630  {
631  const auto s1 = solver_fn(psm1, posA.linear, posB.linear);
632  posA += s1.pos_a;
633  posB += s1.pos_b;
634  const auto psm0_prime = GetPSM(pc.manifold, 0,
635  GetTransformation(posA, localCenterA),
636  GetTransformation(posB, localCenterB));
637  const auto s0 = solver_fn(psm0_prime, posA.linear, posB.linear);
638  posA += s0.pos_a;
639  posB += s0.pos_b;
640  return d2::PositionSolution{posA, posB, s1.min_separation};
641  }
642 #endif
643  // reaches here if one or both psm separation values was NaN (and NDEBUG is defined).
644  }
645  default: break;
646  }
647  return d2::PositionSolution{posA, posB, std::numeric_limits<Length>::infinity()};
648 }
649 
650 } // namespace GaussSeidel
651 
652 } // namespace playrho