WeldJoint.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 namespace playrho {
30 namespace d2 {
31 
32 namespace {
33 
34 Mat33 GetMat33(InvMass invMassA, Length2 rA, InvRotInertia invRotInertiaA,
35  InvMass invMassB, Length2 rB, InvRotInertia invRotInertiaB)
36 {
37  const auto exx = InvMass{
38  invMassA + Square(GetY(rA)) * invRotInertiaA / SquareRadian +
39  invMassB + Square(GetY(rB)) * invRotInertiaB / SquareRadian
40  };
41  const auto eyx = InvMass{
42  -GetY(rA) * GetX(rA) * invRotInertiaA / SquareRadian +
43  -GetY(rB) * GetX(rB) * invRotInertiaB / SquareRadian
44  };
45  const auto ezx = InvMass{
46  -GetY(rA) * invRotInertiaA * Meter / SquareRadian +
47  -GetY(rB) * invRotInertiaB * Meter / SquareRadian
48  };
49  const auto eyy = InvMass{
50  invMassA + Square(GetX(rA)) * invRotInertiaA / SquareRadian +
51  invMassB + Square(GetX(rB)) * invRotInertiaB / SquareRadian
52  };
53  const auto ezy = InvMass{
54  GetX(rA) * invRotInertiaA * Meter / SquareRadian +
55  GetX(rB) * invRotInertiaB * Meter / SquareRadian
56  };
57  const auto ezz = InvMass{(invRotInertiaA + invRotInertiaB) * SquareMeter / SquareRadian};
58 
59  Mat33 K;
60  GetX(GetX(K)) = StripUnit(exx);
61  GetX(GetY(K)) = StripUnit(eyx);
62  GetX(GetZ(K)) = StripUnit(ezx);
63  GetY(GetX(K)) = GetX(GetY(K));
64  GetY(GetY(K)) = StripUnit(eyy);
65  GetY(GetZ(K)) = StripUnit(ezy);
66  GetZ(GetX(K)) = GetX(GetZ(K));
67  GetZ(GetY(K)) = GetY(GetZ(K));
68  GetZ(GetZ(K)) = StripUnit(ezz);
69  return K;
70 }
71 
72 } // unnamed namespace
73 
74 // Point-to-point constraint
75 // C = p2 - p1
76 // Cdot = v2 - v1
77 // = v2 + cross(w2, r2) - v1 - cross(w1, r1)
78 // J = [-I -r1_skew I r2_skew ]
79 // Identity used:
80 // w k % (rx i + ry j) = w * (-ry i + rx j)
81 
82 // Angle constraint
83 // C = angle2 - angle1 - referenceAngle
84 // Cdot = w2 - w1
85 // J = [0 0 -1 0 0 1]
86 // K = invI1 + invI2
87 
89  Joint(def),
90  m_localAnchorA(def.localAnchorA),
91  m_localAnchorB(def.localAnchorB),
92  m_referenceAngle(def.referenceAngle),
93  m_frequency(def.frequency),
94  m_dampingRatio(def.dampingRatio)
95 {
96  // Intentionally empty.
97 }
98 
99 void WeldJoint::Accept(JointVisitor& visitor) const
100 {
101  visitor.Visit(*this);
102 }
103 
105 {
106  visitor.Visit(*this);
107 }
108 
109 void WeldJoint::InitVelocityConstraints(BodyConstraintsMap& bodies, const StepConf& step,
110  const ConstraintSolverConf&)
111 {
112  auto& bodyConstraintA = At(bodies, GetBodyA());
113  auto& bodyConstraintB = At(bodies, GetBodyB());
114 
115  auto velA = bodyConstraintA->GetVelocity();
116  const auto posA = bodyConstraintA->GetPosition();
117  const auto invMassA = bodyConstraintA->GetInvMass();
118  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
119 
120  auto velB = bodyConstraintB->GetVelocity();
121  const auto posB = bodyConstraintB->GetPosition();
122  const auto invMassB = bodyConstraintB->GetInvMass();
123  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
124 
125  const auto qA = UnitVec::Get(posA.angular);
126  const auto qB = UnitVec::Get(posB.angular);
127 
128  m_rA = Rotate(m_localAnchorA - bodyConstraintA->GetLocalCenter(), qA);
129  m_rB = Rotate(m_localAnchorB - bodyConstraintB->GetLocalCenter(), qB);
130 
131  // J = [-I -r1_skew I r2_skew]
132  // [ 0 -1 0 1]
133  // r_skew = [-ry; rx]
134 
135  // Matlab
136  // K = [ invMassA+r1y^2*invRotInertiaA+invMassB+r2y^2*invRotInertiaB, -r1y*invRotInertiaA*r1x-r2y*invRotInertiaB*r2x, -r1y*invRotInertiaA-r2y*invRotInertiaB]
137  // [ -r1y*invRotInertiaA*r1x-r2y*invRotInertiaB*r2x, invMassA+r1x^2*invRotInertiaA+invMassB+r2x^2*invRotInertiaB, r1x*invRotInertiaA+r2x*invRotInertiaB]
138  // [ -r1y*invRotInertiaA-r2y*invRotInertiaB, r1x*invRotInertiaA+r2x*invRotInertiaB, invRotInertiaA+invRotInertiaB]
139 
140  const auto K = GetMat33(invMassA, m_rA, invRotInertiaA, invMassB, m_rB, invRotInertiaB);
141  if (m_frequency > 0_Hz)
142  {
143  m_mass = GetInverse22(K);
144 
145  // InvRotInertia is L^-2 M^-1 QP^2
146  // RotInertia is L^2 M QP^-2
147  auto invRotInertia = InvRotInertia{invRotInertiaA + invRotInertiaB};
148  const auto rotInertia = (invRotInertia > InvRotInertia{0})? Real{1} / invRotInertia: RotInertia{0};
149 
150  const auto C = Angle{posB.angular - posA.angular - m_referenceAngle};
151  const auto omega = Real(2) * Pi * m_frequency; // T^-1
152  const auto d = Real(2) * rotInertia * m_dampingRatio * omega;
153 
154  // Spring stiffness: L^2 M QP^-2 T^-2
155  const auto k = rotInertia * omega * omega;
156 
157  // magic formulas
158  const auto h = step.GetTime();
159  const auto invGamma = RotInertia{h * (d + h * k)};
160  m_gamma = (invGamma != RotInertia{0})? Real{1} / invGamma: InvRotInertia{0};
161  // QP * T * L^2 M QP^-2 T^-2 * L^-2 M^-1 QP^2 is: QP T^-1
162  m_bias = AngularVelocity{C * h * k * m_gamma};
163 
164  invRotInertia += m_gamma;
165  GetZ(GetZ(m_mass)) = StripUnit((invRotInertia != InvRotInertia{0}) ?
166  Real{1} / invRotInertia : RotInertia{0});
167  }
168  else if (GetZ(GetZ(K)) == 0)
169  {
170  m_mass = GetInverse22(K);
171  m_gamma = InvRotInertia{0};
172  m_bias = 0_rpm;
173  }
174  else
175  {
176  m_mass = GetSymInverse33(K);
177  m_gamma = InvRotInertia{0};
178  m_bias = 0_rpm;
179  }
180 
181  if (step.doWarmStart)
182  {
183  // Scale impulses to support a variable time step.
184  m_impulse *= step.dtRatio;
185 
186  const auto P = Momentum2{GetX(m_impulse) * NewtonSecond, GetY(m_impulse) * NewtonSecond};
187 
188  // AngularMomentum is L^2 M T^-1 QP^-1.
189  const auto L = AngularMomentum{GetZ(m_impulse) * SquareMeter * Kilogram / (Second * Radian)};
190  const auto LA = L + AngularMomentum{Cross(m_rA, P) / Radian};
191  const auto LB = L + AngularMomentum{Cross(m_rB, P) / Radian};
192 
193  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
194  velB += Velocity{invMassB * P, invRotInertiaB * LB};
195  }
196  else
197  {
198  m_impulse = Vec3{};
199  }
200 
201  bodyConstraintA->SetVelocity(velA);
202  bodyConstraintB->SetVelocity(velB);
203 }
204 
205 bool WeldJoint::SolveVelocityConstraints(BodyConstraintsMap& bodies, const StepConf&)
206 {
207  auto& bodyConstraintA = At(bodies, GetBodyA());
208  auto& bodyConstraintB = At(bodies, GetBodyB());
209 
210  const auto oldVelA = bodyConstraintA->GetVelocity();
211  auto velA = oldVelA;
212  const auto invMassA = bodyConstraintA->GetInvMass();
213  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
214 
215  const auto oldVelB = bodyConstraintB->GetVelocity();
216  auto velB = oldVelB;
217  const auto invMassB = bodyConstraintB->GetInvMass();
218  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
219 
220  if (m_frequency > 0_Hz)
221  {
222  const auto Cdot2 = velB.angular - velA.angular;
223 
224  // InvRotInertia is L^-2 M^-1 QP^2. Angular velocity is QP T^-1
225  const auto gamma = AngularVelocity{m_gamma * GetZ(m_impulse) * SquareMeter * Kilogram / (Radian * Second)};
226 
227  // AngularMomentum is L^2 M T^-1 QP^-1.
228  const auto impulse2 = -GetZ(GetZ(m_mass)) * StripUnit(Cdot2 + m_bias + gamma);
229  GetZ(m_impulse) += impulse2;
230 
231  velA.angular -= AngularVelocity{invRotInertiaA * impulse2 * SquareMeter * Kilogram / (Second * Radian)};
232  velB.angular += AngularVelocity{invRotInertiaB * impulse2 * SquareMeter * Kilogram / (Second * Radian)};
233 
234  const auto vb = velB.linear + LinearVelocity2{(GetRevPerpendicular(m_rB) * (velB.angular / Radian))};
235  const auto va = velA.linear + LinearVelocity2{(GetRevPerpendicular(m_rA) * (velA.angular / Radian))};
236 
237  const auto Cdot1 = vb - va;
238 
239  const auto impulse1 = -Transform(Vec2{GetX(Cdot1) / MeterPerSecond, GetY(Cdot1) / MeterPerSecond}, m_mass);
240  GetX(m_impulse) += GetX(impulse1);
241  GetY(m_impulse) += GetY(impulse1);
242 
243  const auto P = Momentum2{GetX(impulse1) * NewtonSecond, GetY(impulse1) * NewtonSecond};
244  const auto LA = AngularMomentum{Cross(m_rA, P) / Radian};
245  const auto LB = AngularMomentum{Cross(m_rB, P) / Radian};
246 
247  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
248  velB += Velocity{invMassB * P, invRotInertiaB * LB};
249  }
250  else
251  {
252  const auto vb = velB.linear + LinearVelocity2{(GetRevPerpendicular(m_rB) * (velB.angular / Radian))};
253  const auto va = velA.linear + LinearVelocity2{(GetRevPerpendicular(m_rA) * (velA.angular / Radian))};
254 
255  const auto Cdot1 = vb - va;
256  const auto Cdot2 = Real{(velB.angular - velA.angular) / RadianPerSecond};
257  const auto Cdot = Vec3{GetX(Cdot1) / MeterPerSecond, GetY(Cdot1) / MeterPerSecond, Cdot2};
258 
259  const auto impulse = -Transform(Cdot, m_mass);
260  m_impulse += impulse;
261 
262  const auto P = Momentum2{GetX(impulse) * NewtonSecond, GetY(impulse) * NewtonSecond};
263 
264  // AngularMomentum is L^2 M T^-1 QP^-1.
265  const auto L = AngularMomentum{GetZ(impulse) * SquareMeter * Kilogram / (Second * Radian)};
266  const auto LA = L + AngularMomentum{Cross(m_rA, P) / Radian};
267  const auto LB = L + AngularMomentum{Cross(m_rB, P) / Radian};
268 
269  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
270  velB += Velocity{invMassB * P, invRotInertiaB * LB};
271  }
272 
273  if ((velA != oldVelA) || (velB != oldVelB))
274  {
275  bodyConstraintA->SetVelocity(velA);
276  bodyConstraintB->SetVelocity(velB);
277  return false;
278  }
279  return true;
280 }
281 
282 bool WeldJoint::SolvePositionConstraints(BodyConstraintsMap& bodies, const ConstraintSolverConf& conf) const
283 {
284  auto& bodyConstraintA = At(bodies, GetBodyA());
285  auto& bodyConstraintB = At(bodies, GetBodyB());
286 
287  auto posA = bodyConstraintA->GetPosition();
288  auto posB = bodyConstraintB->GetPosition();
289 
290  const auto qA = UnitVec::Get(posA.angular);
291  const auto qB = UnitVec::Get(posB.angular);
292 
293  const auto invMassA = bodyConstraintA->GetInvMass();
294  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
295  const auto invMassB = bodyConstraintB->GetInvMass();
296  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
297 
298  const auto rA = Rotate(m_localAnchorA - bodyConstraintA->GetLocalCenter(), qA);
299  const auto rB = Rotate(m_localAnchorB - bodyConstraintB->GetLocalCenter(), qB);
300 
301  auto positionError = 0_m;
302  auto angularError = 0_deg;
303 
304  const auto K = GetMat33(invMassA, rA, invRotInertiaA, invMassB, rB, invRotInertiaB);
305  if (m_frequency > 0_Hz)
306  {
307  const auto C1 = Length2{(posB.linear + rB) - (posA.linear + rA)};
308 
309  positionError = GetMagnitude(C1);
310  angularError = 0_deg;
311 
312  const auto P = -Solve22(K, C1) * Kilogram;
313  const auto LA = Cross(rA, P) / Radian;
314  const auto LB = Cross(rB, P) / Radian;
315 
316  posA -= Position{invMassA * P, invRotInertiaA * LA};
317  posB += Position{invMassB * P, invRotInertiaB * LB};
318  }
319  else
320  {
321  const auto C1 = Length2{(posB.linear + rB) - (posA.linear + rA)};
322  const auto C2 = Angle{posB.angular - posA.angular - m_referenceAngle};
323 
324  positionError = GetMagnitude(C1);
325  angularError = abs(C2);
326 
327  const auto C = Vec3{StripUnit(GetX(C1)), StripUnit(GetY(C1)), StripUnit(C2)};
328 
329  Vec3 impulse;
330  if (GetZ(GetZ(K)) > 0)
331  {
332  impulse = -Solve33(K, C);
333  }
334  else
335  {
336  const auto impulse2 = -Solve22(K, GetVec2(C1));
337  impulse = Vec3{GetX(impulse2), GetY(impulse2), 0};
338  }
339 
340  const auto P = Length2{GetX(impulse) * Meter, GetY(impulse) * Meter} * Kilogram;
341  const auto L = GetZ(impulse) * Kilogram * SquareMeter / Radian;
342  const auto LA = L + Cross(rA, P) / Radian;
343  const auto LB = L + Cross(rB, P) / Radian;
344 
345  posA -= Position{invMassA * P, invRotInertiaA * LA};
346  posB += Position{invMassB * P, invRotInertiaB * LB};
347  }
348 
349  bodyConstraintA->SetPosition(posA);
350  bodyConstraintB->SetPosition(posB);
351 
352  return (positionError <= conf.linearSlop) && (angularError <= conf.angularSlop);
353 }
354 
356 {
357  return GetWorldPoint(*GetBodyA(), GetLocalAnchorA());
358 }
359 
361 {
362  return GetWorldPoint(*GetBodyB(), GetLocalAnchorB());
363 }
364 
366 {
367  return Momentum2{GetX(m_impulse) * NewtonSecond, GetY(m_impulse) * NewtonSecond};
368 }
369 
371 {
372  // AngularMomentum is L^2 M T^-1 QP^-1
373  return AngularMomentum{GetZ(m_impulse) * SquareMeter * Kilogram / (Second * Radian)};
374 }
375 
376 } // namespace d2
377 } // namespace playrho