RevoluteJoint.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  *
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  *
13  * 1. The origin of this software must not be misrepresented; you must not
14  * claim that you wrote the original software. If you use this software
15  * in a product, an acknowledgment in the product documentation would be
16  * appreciated but is not required.
17  * 2. Altered source versions must be plainly marked as such, and must not be
18  * misrepresented as being the original software.
19  * 3. This notice may not be removed or altered from any source distribution.
20  */
21 
28 
29 #include <algorithm>
30 
31 namespace playrho {
32 namespace d2 {
33 
34 namespace {
35 
36 Mat33 GetMat33(InvMass invMassA, Length2 rA, InvRotInertia invRotInertiaA,
37  InvMass invMassB, Length2 rB, InvRotInertia invRotInertiaB)
38 {
39  const auto totInvI = invRotInertiaA + invRotInertiaB;
40 
41  const auto exx = InvMass{
42  invMassA + (Square(GetY(rA)) * invRotInertiaA / SquareRadian) +
43  invMassB + (Square(GetY(rB)) * invRotInertiaB / SquareRadian)
44  };
45  const auto eyx = InvMass{
46  (-GetY(rA) * GetX(rA) * invRotInertiaA / SquareRadian) +
47  (-GetY(rB) * GetX(rB) * invRotInertiaB / SquareRadian)
48  };
49  const auto ezx = InvMass{
50  (-GetY(rA) * invRotInertiaA * Meter / SquareRadian) +
51  (-GetY(rB) * invRotInertiaB * Meter / SquareRadian)
52  };
53  const auto eyy = InvMass{
54  invMassA + (Square(GetX(rA)) * invRotInertiaA / SquareRadian) +
55  invMassB + (Square(GetX(rB)) * invRotInertiaB / SquareRadian)
56  };
57  const auto ezy = InvMass{
58  (GetX(rA) * invRotInertiaA * Meter / SquareRadian) +
59  (GetX(rB) * invRotInertiaB * Meter / SquareRadian)
60  };
61 
62  auto mass = Mat33{};
63  GetX(GetX(mass)) = StripUnit(exx);
64  GetX(GetY(mass)) = StripUnit(eyx);
65  GetX(GetZ(mass)) = StripUnit(ezx);
66  GetY(GetX(mass)) = GetX(GetY(mass));
67  GetY(GetY(mass)) = StripUnit(eyy);
68  GetY(GetZ(mass)) = StripUnit(ezy);
69  GetZ(GetX(mass)) = GetX(GetZ(mass));
70  GetZ(GetY(mass)) = GetY(GetZ(mass));
71  GetZ(GetZ(mass)) = StripUnit(totInvI);
72  return mass;
73 }
74 
75 } // unnamed namespace
76 
77 // Point-to-point constraint
78 // C = p2 - p1
79 // Cdot = v2 - v1
80 // = v2 + cross(w2, r2) - v1 - cross(w1, r1)
81 // J = [-I -r1_skew I r2_skew ]
82 // Identity used:
83 // w k % (rx i + ry j) = w * (-ry i + rx j)
84 
85 // Motor constraint
86 // Cdot = w2 - w1
87 // J = [0 0 -1 0 0 1]
88 // K = invI1 + invI2
89 
91  Joint{def},
92  m_localAnchorA{def.localAnchorA},
93  m_localAnchorB{def.localAnchorB},
94  m_enableMotor{def.enableMotor},
95  m_maxMotorTorque{def.maxMotorTorque},
96  m_motorSpeed{def.motorSpeed},
97  m_enableLimit{def.enableLimit},
98  m_referenceAngle{def.referenceAngle},
99  m_lowerAngle{def.lowerAngle},
100  m_upperAngle{def.upperAngle}
101 {
102  // Intentionally empty.
103 }
104 
106 {
107  visitor.Visit(*this);
108 }
109 
111 {
112  visitor.Visit(*this);
113 }
114 
115 void RevoluteJoint::InitVelocityConstraints(BodyConstraintsMap& bodies,
116  const StepConf& step,
117  const ConstraintSolverConf& conf)
118 {
119  auto& bodyConstraintA = At(bodies, GetBodyA());
120  auto& bodyConstraintB = At(bodies, GetBodyB());
121 
122  const auto invMassA = bodyConstraintA->GetInvMass();
123  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
124  const auto aA = bodyConstraintA->GetPosition().angular;
125  auto velA = bodyConstraintA->GetVelocity();
126 
127  const auto invMassB = bodyConstraintB->GetInvMass();
128  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
129  const auto aB = bodyConstraintB->GetPosition().angular;
130  auto velB = bodyConstraintB->GetVelocity();
131 
132  const auto qA = UnitVec::Get(aA);
133  const auto qB = UnitVec::Get(aB);
134 
135  m_rA = Rotate(m_localAnchorA - bodyConstraintA->GetLocalCenter(), qA);
136  m_rB = Rotate(m_localAnchorB - bodyConstraintB->GetLocalCenter(), qB);
137 
138  // J = [-I -r1_skew I r2_skew]
139  // [ 0 -1 0 1]
140  // r_skew = [-ry; rx]
141 
142  // Matlab
143  // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB]
144  // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB]
145  // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB]
146 
147  const auto totInvI = invRotInertiaA + invRotInertiaB;
148  const auto fixedRotation = (totInvI == InvRotInertia{0});
149 
150  m_mass = GetMat33(invMassA, m_rA, invRotInertiaA, invMassB, m_rB, invRotInertiaB);
151  m_motorMass = (totInvI > InvRotInertia{0})? RotInertia{Real{1} / totInvI}: RotInertia{0};
152 
153  if (!m_enableMotor || fixedRotation)
154  {
155  m_motorImpulse = 0;
156  }
157 
158  if (m_enableLimit && !fixedRotation)
159  {
160  const auto jointAngle = aB - aA - GetReferenceAngle();
161  if (abs(m_upperAngle - m_lowerAngle) < (conf.angularSlop * 2))
162  {
163  m_limitState = e_equalLimits;
164  }
165  else if (jointAngle <= m_lowerAngle)
166  {
167  if (m_limitState != e_atLowerLimit)
168  {
169  m_limitState = e_atLowerLimit;
170  GetZ(m_impulse) = 0;
171  }
172  }
173  else if (jointAngle >= m_upperAngle)
174  {
175  if (m_limitState != e_atUpperLimit)
176  {
177  m_limitState = e_atUpperLimit;
178  GetZ(m_impulse) = 0;
179  }
180  }
181  else // jointAngle > m_lowerAngle && jointAngle < m_upperAngle
182  {
183  m_limitState = e_inactiveLimit;
184  GetZ(m_impulse) = 0;
185  }
186  }
187  else
188  {
189  m_limitState = e_inactiveLimit;
190  }
191 
192  if (step.doWarmStart)
193  {
194  // Scale impulses to support a variable time step.
195  m_impulse *= step.dtRatio;
196  m_motorImpulse *= step.dtRatio;
197 
198  const auto P = Momentum2{GetX(m_impulse) * NewtonSecond, GetY(m_impulse) * NewtonSecond};
199 
200  // AngularMomentum is L^2 M T^-1 QP^-1.
201  const auto L = AngularMomentum{
202  m_motorImpulse + (GetZ(m_impulse) * SquareMeter * Kilogram / (Second * Radian))
203  };
204  const auto LA = AngularMomentum{Cross(m_rA, P) / Radian} + L;
205  const auto LB = AngularMomentum{Cross(m_rB, P) / Radian} + L;
206 
207  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
208  velB += Velocity{invMassB * P, invRotInertiaB * LB};
209  }
210  else
211  {
212  m_impulse = Vec3{};
213  m_motorImpulse = 0;
214  }
215 
216  bodyConstraintA->SetVelocity(velA);
217  bodyConstraintB->SetVelocity(velB);
218 }
219 
220 bool RevoluteJoint::SolveVelocityConstraints(BodyConstraintsMap& bodies, const StepConf& step)
221 {
222  auto& bodyConstraintA = At(bodies, GetBodyA());
223  auto& bodyConstraintB = At(bodies, GetBodyB());
224 
225  const auto oldVelA = bodyConstraintA->GetVelocity();
226  auto velA = oldVelA;
227  const auto invMassA = bodyConstraintA->GetInvMass();
228  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
229 
230  const auto oldVelB = bodyConstraintB->GetVelocity();
231  auto velB = oldVelB;
232  const auto invMassB = bodyConstraintB->GetInvMass();
233  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
234 
235  const auto fixedRotation = (invRotInertiaA + invRotInertiaB == InvRotInertia{0});
236 
237  // Solve motor constraint.
238  if (m_enableMotor && (m_limitState != e_equalLimits) && !fixedRotation)
239  {
240  const auto impulse = AngularMomentum{-m_motorMass * (velB.angular - velA.angular - m_motorSpeed)};
241  const auto oldImpulse = m_motorImpulse;
242  const auto maxImpulse = step.GetTime() * m_maxMotorTorque;
243  m_motorImpulse = std::clamp(m_motorImpulse + impulse, -maxImpulse, maxImpulse);
244  const auto incImpulse = m_motorImpulse - oldImpulse;
245 
246  velA.angular -= invRotInertiaA * incImpulse;
247  velB.angular += invRotInertiaB * incImpulse;
248  }
249 
250  const auto vb = velB.linear + GetRevPerpendicular(m_rB) * (velB.angular / Radian);
251  const auto va = velA.linear + GetRevPerpendicular(m_rA) * (velA.angular / Radian);
252  const auto vDelta = vb - va;
253 
254  // Solve limit constraint.
255  if (m_enableLimit && (m_limitState != e_inactiveLimit) && !fixedRotation)
256  {
257  const auto Cdot = Vec3{
258  GetX(vDelta) / MeterPerSecond,
259  GetY(vDelta) / MeterPerSecond,
260  (velB.angular - velA.angular) / RadianPerSecond
261  };
262  auto impulse = -Solve33(m_mass, Cdot);
263 
264  auto UpdateImpulseProc = [&]() {
265  const auto rhs = -Vec2{
266  GetX(vDelta) / MeterPerSecond,
267  GetY(vDelta) / MeterPerSecond
268  } + GetZ(m_impulse) * Vec2{GetX(GetZ(m_mass)), GetY(GetZ(m_mass))};
269  const auto reduced = Solve22(m_mass, rhs);
270  GetX(impulse) = GetX(reduced);
271  GetY(impulse) = GetY(reduced);
272  GetZ(impulse) = -GetZ(m_impulse);
273  GetX(m_impulse) += GetX(reduced);
274  GetY(m_impulse) += GetY(reduced);
275  GetZ(m_impulse) = 0;
276  };
277 
278  switch (m_limitState)
279  {
280  case e_atLowerLimit:
281  {
282  const auto newImpulse = GetZ(m_impulse) + GetZ(impulse);
283  if (newImpulse < 0)
284  {
285  UpdateImpulseProc();
286  }
287  else
288  {
289  m_impulse += impulse;
290  }
291  break;
292  }
293  case e_atUpperLimit:
294  {
295  const auto newImpulse = GetZ(m_impulse) + GetZ(impulse);
296  if (newImpulse > 0)
297  {
298  UpdateImpulseProc();
299  }
300  else
301  {
302  m_impulse += impulse;
303  }
304  break;
305  }
306  default:
307  assert(m_limitState == e_equalLimits);
308  m_impulse += impulse;
309  break;
310  }
311 
312  const auto P = Momentum2{GetX(impulse) * NewtonSecond, GetY(impulse) * NewtonSecond};
313  const auto L = AngularMomentum{GetZ(impulse) * SquareMeter * Kilogram / (Second * Radian)};
314  const auto LA = AngularMomentum{Cross(m_rA, P) / Radian} + L;
315  const auto LB = AngularMomentum{Cross(m_rB, P) / Radian} + L;
316 
317  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
318  velB += Velocity{invMassB * P, invRotInertiaB * LB};
319  }
320  else
321  {
322  // Solve point-to-point constraint
323  const auto impulse = Solve22(m_mass, -Vec2{
324  get<0>(vDelta) / MeterPerSecond, get<1>(vDelta) / MeterPerSecond
325  });
326 
327  GetX(m_impulse) += GetX(impulse);
328  GetY(m_impulse) += GetY(impulse);
329 
330  const auto P = Momentum2{GetX(impulse) * NewtonSecond, GetY(impulse) * NewtonSecond};
331  const auto LA = AngularMomentum{Cross(m_rA, P) / Radian};
332  const auto LB = AngularMomentum{Cross(m_rB, P) / Radian};
333 
334  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
335  velB += Velocity{invMassB * P, invRotInertiaB * LB};
336  }
337 
338  if ((velA != oldVelA) || (velB != oldVelB))
339  {
340  bodyConstraintA->SetVelocity(velA);
341  bodyConstraintB->SetVelocity(velB);
342  return false;
343  }
344  return true;
345 }
346 
347 bool RevoluteJoint::SolvePositionConstraints(BodyConstraintsMap& bodies, const ConstraintSolverConf& conf) const
348 {
349  auto& bodyConstraintA = At(bodies, GetBodyA());
350  auto& bodyConstraintB = At(bodies, GetBodyB());
351 
352  auto posA = bodyConstraintA->GetPosition();
353  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
354 
355  auto posB = bodyConstraintB->GetPosition();
356  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
357 
358  const auto fixedRotation = ((invRotInertiaA + invRotInertiaB) == InvRotInertia{0});
359 
360  // Solve angular limit constraint.
361  auto angularError = 0_rad;
362  if (m_enableLimit && (m_limitState != e_inactiveLimit) && !fixedRotation)
363  {
364  const auto angle = posB.angular - posA.angular - GetReferenceAngle();
365 
366  // RotInertia is L^2 M QP^-2, Angle is QP, so RotInertia * Angle is L^2 M QP^-1.
367  auto limitImpulse = Real{0} * SquareMeter * Kilogram / Radian;
368 
369  switch (m_limitState)
370  {
371  case e_atLowerLimit:
372  {
373  auto C = angle - m_lowerAngle;
374  angularError = -C;
375 
376  // Prevent large angular corrections and allow some slop.
377  C = std::clamp(C + conf.angularSlop, -conf.maxAngularCorrection, 0_rad);
378  limitImpulse = -m_motorMass * C;
379  break;
380  }
381  case e_atUpperLimit:
382  {
383  auto C = angle - m_upperAngle;
384  angularError = C;
385 
386  // Prevent large angular corrections and allow some slop.
387  C = std::clamp(C - conf.angularSlop, 0_rad, conf.maxAngularCorrection);
388  limitImpulse = -m_motorMass * C;
389  break;
390  }
391  default:
392  {
393  assert(m_limitState == e_equalLimits);
394  // Prevent large angular corrections
395  const auto C = std::clamp(angle - m_lowerAngle,
396  -conf.maxAngularCorrection, conf.maxAngularCorrection);
397  limitImpulse = -m_motorMass * C;
398  angularError = abs(C);
399  break;
400  }
401  }
402 
403  // InvRotInertia is L^-2 M^-1 QP^2, limitImpulse is L^2 M QP^-1, so product is QP.
404  posA.angular -= invRotInertiaA * limitImpulse;
405  posB.angular += invRotInertiaB * limitImpulse;
406  }
407 
408  // Solve point-to-point constraint.
409  auto positionError = 0_m2;
410  {
411  const auto qA = UnitVec::Get(posA.angular);
412  const auto qB = UnitVec::Get(posB.angular);
413 
414  const auto rA = Length2{Rotate(m_localAnchorA - bodyConstraintA->GetLocalCenter(), qA)};
415  const auto rB = Length2{Rotate(m_localAnchorB - bodyConstraintB->GetLocalCenter(), qB)};
416 
417  const auto C = (posB.linear + rB) - (posA.linear + rA);
418  positionError = GetMagnitudeSquared(C);
419 
420  const auto invMassA = bodyConstraintA->GetInvMass();
421  const auto invMassB = bodyConstraintB->GetInvMass();
422 
423  const auto exx = InvMass{
424  invMassA + (invRotInertiaA * Square(GetY(rA)) / SquareRadian) +
425  invMassB + (invRotInertiaB * Square(GetY(rB)) / SquareRadian)
426  };
427  const auto exy = InvMass{
428  (-invRotInertiaA * GetX(rA) * GetY(rA) / SquareRadian) +
429  (-invRotInertiaB * GetX(rB) * GetY(rB) / SquareRadian)
430  };
431  const auto eyy = InvMass{
432  invMassA + (invRotInertiaA * Square(GetX(rA)) / SquareRadian) +
433  invMassB + (invRotInertiaB * Square(GetX(rB)) / SquareRadian)
434  };
435 
436  InvMass22 K;
437  GetX(GetX(K)) = exx;
438  GetY(GetX(K)) = exy;
439  GetX(GetY(K)) = exy;
440  GetY(GetY(K)) = eyy;
441  const auto P = -Solve(K, C);
442 
443  posA -= Position{invMassA * P, invRotInertiaA * Cross(rA, P) / Radian};
444  posB += Position{invMassB * P, invRotInertiaB * Cross(rB, P) / Radian};
445  }
446 
447  bodyConstraintA->SetPosition(posA);
448  bodyConstraintB->SetPosition(posB);
449 
450  return (positionError <= Square(conf.linearSlop)) && (angularError <= conf.angularSlop);
451 }
452 
454 {
455  return GetWorldPoint(*GetBodyA(), GetLocalAnchorA());
456 }
457 
459 {
460  return GetWorldPoint(*GetBodyB(), GetLocalAnchorB());
461 }
462 
464 {
465  return Momentum2{GetX(m_impulse) * NewtonSecond, GetY(m_impulse) * NewtonSecond};
466 }
467 
469 {
470  // AngularMomentum is L^2 M T^-1 QP^-1.
471  return GetZ(m_impulse) * SquareMeter * Kilogram / (Second * Radian);
472 }
473 
475 {
476  if (m_enableMotor != flag)
477  {
478  m_enableMotor = flag;
479 
480  // XXX Should these be called regardless of whether the state changed?
481  GetBodyA()->SetAwake();
482  GetBodyB()->SetAwake();
483  }
484 }
485 
487 {
488  if (m_motorSpeed != speed)
489  {
490  m_motorSpeed = speed;
491 
492  // XXX Should these be called regardless of whether the state changed?
493  GetBodyA()->SetAwake();
494  GetBodyB()->SetAwake();
495  }
496 }
497 
499 {
500  if (m_maxMotorTorque != torque)
501  {
502  m_maxMotorTorque = torque;
503 
504  // XXX Should these be called regardless of whether the state changed?
505  GetBodyA()->SetAwake();
506  GetBodyB()->SetAwake();
507  }
508 }
509 
511 {
512  if (flag != m_enableLimit)
513  {
514  m_enableLimit = flag;
515  GetZ(m_impulse) = 0;
516 
517  GetBodyA()->SetAwake();
518  GetBodyB()->SetAwake();
519  }
520 }
521 
523 {
524  assert(lower <= upper);
525 
526  if ((lower != m_lowerAngle) || (upper != m_upperAngle))
527  {
528  GetZ(m_impulse) = 0;
529  m_lowerAngle = lower;
530  m_upperAngle = upper;
531 
532  GetBodyA()->SetAwake();
533  GetBodyB()->SetAwake();
534  }
535 }
536 
538 {
539  return joint.GetBodyB()->GetAngle() - joint.GetBodyA()->GetAngle() - joint.GetReferenceAngle();
540 }
541 
543 {
544  return joint.GetBodyB()->GetVelocity().angular - joint.GetBodyA()->GetVelocity().angular;
545 }
546 
547 } // namespace d2
548 } // namespace playrho