WheelJoint.cpp
Go to the documentation of this file.
1 /*
2  * Original work Copyright (c) 2006-2007 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 // Linear constraint (point-to-line)
33 // d = pB - pA = xB + rB - xA - rA
34 // C = dot(ay, d)
35 // Cdot = dot(d, cross(wA, ay)) + dot(ay, vB + cross(wB, rB) - vA - cross(wA, rA))
36 // = -dot(ay, vA) - dot(cross(d + rA, ay), wA) + dot(ay, vB) + dot(cross(rB, ay), vB)
37 // J = [-ay, -cross(d + rA, ay), ay, cross(rB, ay)]
38 
39 // Spring linear constraint
40 // C = dot(ax, d)
41 // Cdot = = -dot(ax, vA) - dot(cross(d + rA, ax), wA) + dot(ax, vB) + dot(cross(rB, ax), vB)
42 // J = [-ax -cross(d+rA, ax) ax cross(rB, ax)]
43 
44 // Motor rotational constraint
45 // Cdot = wB - wA
46 // J = [0 0 -1 0 0 1]
47 
49  Joint(def),
50  m_localAnchorA(def.localAnchorA),
51  m_localAnchorB(def.localAnchorB),
52  m_localXAxisA(def.localAxisA),
53  m_localYAxisA(GetRevPerpendicular(m_localXAxisA)),
54  m_frequency(def.frequency),
55  m_dampingRatio(def.dampingRatio),
56  m_maxMotorTorque(def.maxMotorTorque),
57  m_motorSpeed(def.motorSpeed),
58  m_enableMotor(def.enableMotor)
59 {
60  // Intentionally empty.
61 }
62 
63 void WheelJoint::Accept(JointVisitor& visitor) const
64 {
65  visitor.Visit(*this);
66 }
67 
69 {
70  visitor.Visit(*this);
71 }
72 
73 void WheelJoint::InitVelocityConstraints(BodyConstraintsMap& bodies, const StepConf& step, const ConstraintSolverConf&)
74 {
75  auto& bodyConstraintA = At(bodies, GetBodyA());
76  auto& bodyConstraintB = At(bodies, GetBodyB());
77 
78  const auto posA = bodyConstraintA->GetPosition();
79  auto velA = bodyConstraintA->GetVelocity();
80  const auto invMassA = bodyConstraintA->GetInvMass();
81  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
82 
83  const auto posB = bodyConstraintB->GetPosition();
84  auto velB = bodyConstraintB->GetVelocity();
85  const auto invMassB = bodyConstraintB->GetInvMass();
86  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
87 
88  const auto qA = UnitVec::Get(posA.angular);
89  const auto qB = UnitVec::Get(posB.angular);
90 
91  // Compute the effective masses.
92  const auto rA = Length2{Rotate(m_localAnchorA - bodyConstraintA->GetLocalCenter(), qA)};
93  const auto rB = Length2{Rotate(m_localAnchorB - bodyConstraintB->GetLocalCenter(), qB)};
94  const auto dd = Length2{(posB.linear + rB) - (posA.linear + rA)};
95 
96  // Point to line constraint
97  {
98  m_ay = Rotate(m_localYAxisA, qA);
99  m_sAy = Cross(dd + rA, m_ay);
100  m_sBy = Cross(rB, m_ay);
101 
102  const auto invRotMassA = invRotInertiaA * Square(m_sAy) / SquareRadian;
103  const auto invRotMassB = invRotInertiaB * Square(m_sBy) / SquareRadian;
104  const auto invMass = invMassA + invMassB + invRotMassA + invRotMassB;
105 
106  m_mass = (invMass > InvMass{0})? Real{1} / invMass: 0;
107  }
108 
109  // Spring constraint
110  m_springMass = 0_kg;
111  m_bias = 0;
112  m_gamma = 0;
113  if (m_frequency > 0_Hz)
114  {
115  m_ax = Rotate(m_localXAxisA, qA);
116  m_sAx = Cross(dd + rA, m_ax);
117  m_sBx = Cross(rB, m_ax);
118 
119  const auto invRotMassA = invRotInertiaA * Square(m_sAx) / SquareRadian;
120  const auto invRotMassB = invRotInertiaB * Square(m_sBx) / SquareRadian;
121  const auto invMass = invMassA + invMassB + invRotMassA + invRotMassB;
122 
123  if (invMass > InvMass{0})
124  {
125  m_springMass = Real{1} / invMass;
126 
127  const auto C = Length{Dot(dd, m_ax)};
128 
129  // Frequency
130  const auto omega = Real{2} * Pi * m_frequency;
131 
132  // Damping coefficient
133  const auto d = Real{2} * m_springMass * m_dampingRatio * omega;
134 
135  // Spring stiffness
136  const auto k = m_springMass * omega * omega;
137 
138  // magic formulas
139  const auto h = step.GetTime();
140 
141  const auto invGamma = Mass{h * (d + h * k)};
142  m_gamma = (invGamma > 0_kg)? Real{1} / invGamma: 0;
143  m_bias = LinearVelocity{C * h * k * m_gamma};
144 
145  const auto totalInvMass = invMass + m_gamma;
146  m_springMass = (totalInvMass > InvMass{0})? Real{1} / totalInvMass: 0_kg;
147  }
148  }
149  else
150  {
151  m_springImpulse = 0;
152 
153  m_ax = UnitVec::GetZero();
154  m_sAx = 0_m;
155  m_sBx = 0_m;
156  }
157 
158  // Rotational motor
159  if (m_enableMotor)
160  {
161  const auto invRotInertia = invRotInertiaA + invRotInertiaB;
162  m_motorMass = (invRotInertia > InvRotInertia{0})? Real{1} / invRotInertia: RotInertia{0};
163  }
164  else
165  {
166  m_motorMass = RotInertia{0};
167  m_motorImpulse = 0;
168  }
169 
170  if (step.doWarmStart)
171  {
172  // Account for variable time step.
173  m_impulse *= step.dtRatio;
174  m_springImpulse *= step.dtRatio;
175  m_motorImpulse *= step.dtRatio;
176 
177  const auto P = m_impulse * m_ay + m_springImpulse * m_ax;
178 
179  // Momentum is M L T^-1. Length * momentum is L^2 M T^-1
180  // Angular momentum is L^2 M T^-1 QP^-1
181  const auto LA = AngularMomentum{(m_impulse * m_sAy + m_springImpulse * m_sAx) / Radian + m_motorImpulse};
182  const auto LB = AngularMomentum{(m_impulse * m_sBy + m_springImpulse * m_sBx) / Radian + m_motorImpulse};
183 
184  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
185  velB += Velocity{invMassB * P, invRotInertiaB * LB};
186  }
187  else
188  {
189  m_impulse = 0;
190  m_springImpulse = 0;
191  m_motorImpulse = 0;
192  }
193 
194  bodyConstraintA->SetVelocity(velA);
195  bodyConstraintB->SetVelocity(velB);
196 }
197 
198 bool WheelJoint::SolveVelocityConstraints(BodyConstraintsMap& bodies, const StepConf& step)
199 {
200  auto& bodyConstraintA = At(bodies, GetBodyA());
201  auto& bodyConstraintB = At(bodies, GetBodyB());
202 
203  const auto oldVelA = bodyConstraintA->GetVelocity();
204  const auto invMassA = bodyConstraintA->GetInvMass();
205  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
206 
207  const auto oldVelB = bodyConstraintB->GetVelocity();
208  const auto invMassB = bodyConstraintB->GetInvMass();
209  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
210 
211  auto velA = oldVelA;
212  auto velB = oldVelB;
213 
214  // Solve spring constraint
215  {
216  const auto dot = LinearVelocity{Dot(m_ax, velB.linear - velA.linear)};
217  const auto Cdot = dot + m_sBx * velB.angular / Radian - m_sAx * velA.angular / Radian;
218  const auto impulse = -m_springMass * (Cdot + m_bias + m_gamma * m_springImpulse);
219  m_springImpulse += impulse;
220 
221  const auto P = impulse * m_ax;
222  const auto LA = AngularMomentum{impulse * m_sAx / Radian};
223  const auto LB = AngularMomentum{impulse * m_sBx / Radian};
224 
225  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
226  velB += Velocity{invMassB * P, invRotInertiaB * LB};
227  }
228 
229  // Solve rotational motor constraint
230  {
231  const auto Cdot = (velB.angular - velA.angular - m_motorSpeed);
232  auto impulse = AngularMomentum{-m_motorMass * Cdot};
233 
234  const auto oldImpulse = m_motorImpulse;
235  const auto maxImpulse = AngularMomentum{step.GetTime() * m_maxMotorTorque};
236  m_motorImpulse = std::clamp(m_motorImpulse + impulse, -maxImpulse, maxImpulse);
237  impulse = m_motorImpulse - oldImpulse;
238 
239  velA.angular -= AngularVelocity{invRotInertiaA * impulse};
240  velB.angular += AngularVelocity{invRotInertiaB * impulse};
241  }
242 
243  // Solve point to line constraint
244  {
245  const auto dot = LinearVelocity{Dot(m_ay, velB.linear - velA.linear)};
246  const auto Cdot = dot + m_sBy * velB.angular / Radian - m_sAy * velA.angular / Radian;
247  const auto impulse = -m_mass * Cdot;
248  m_impulse += impulse;
249 
250  const auto P = impulse * m_ay;
251  const auto LA = AngularMomentum{impulse * m_sAy / Radian};
252  const auto LB = AngularMomentum{impulse * m_sBy / Radian};
253 
254  velA -= Velocity{invMassA * P, invRotInertiaA * LA};
255  velB += Velocity{invMassB * P, invRotInertiaB * LB};
256  }
257 
258  if ((velA != oldVelA) || (velB != oldVelB))
259  {
260  bodyConstraintA->SetVelocity(velA);
261  bodyConstraintB->SetVelocity(velB);
262  return false;
263  }
264  return true;
265 }
266 
267 bool WheelJoint::SolvePositionConstraints(BodyConstraintsMap& bodies, const ConstraintSolverConf& conf) const
268 {
269  auto& bodyConstraintA = At(bodies, GetBodyA());
270  auto& bodyConstraintB = At(bodies, GetBodyB());
271 
272  auto posA = bodyConstraintA->GetPosition();
273  const auto invMassA = bodyConstraintA->GetInvMass();
274  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
275 
276  auto posB = bodyConstraintB->GetPosition();
277  const auto invMassB = bodyConstraintB->GetInvMass();
278  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
279 
280  const auto qA = UnitVec::Get(posA.angular);
281  const auto qB = UnitVec::Get(posB.angular);
282 
283  const auto rA = Rotate(m_localAnchorA - bodyConstraintA->GetLocalCenter(), qA);
284  const auto rB = Rotate(m_localAnchorB - bodyConstraintB->GetLocalCenter(), qB);
285  const auto d = Length2{(posB.linear - posA.linear) + (rB - rA)};
286 
287  const auto ay = Rotate(m_localYAxisA, qA);
288 
289  const auto sAy = Cross(d + rA, ay);
290  const auto sBy = Cross(rB, ay);
291 
292  const auto C = Length{Dot(d, ay)};
293 
294  const auto invRotMassA = invRotInertiaA * Square(m_sAy) / SquareRadian;
295  const auto invRotMassB = invRotInertiaB * Square(m_sBy) / SquareRadian;
296 
297  const auto k = InvMass{invMassA + invMassB + invRotMassA + invRotMassB};
298 
299  const auto impulse = (k != InvMass{0})? -(C / k): 0 * Kilogram * Meter;
300 
301  const auto P = impulse * ay;
302  const auto LA = impulse * sAy / Radian;
303  const auto LB = impulse * sBy / Radian;
304 
305  posA -= Position{invMassA * P, invRotInertiaA * LA};
306  posB += Position{invMassB * P, invRotInertiaB * LB};
307 
308  bodyConstraintA->SetPosition(posA);
309  bodyConstraintB->SetPosition(posB);
310 
311  return abs(C) <= conf.linearSlop;
312 }
313 
315 {
316  return GetWorldPoint(*GetBodyA(), GetLocalAnchorA());
317 }
318 
320 {
321  return GetWorldPoint(*GetBodyB(), GetLocalAnchorB());
322 }
323 
325 {
326  return m_impulse * m_ay + m_springImpulse * m_ax;
327 }
328 
329 void WheelJoint::EnableMotor(bool flag)
330 {
331  if (m_enableMotor != flag)
332  {
333  m_enableMotor = flag;
334 
335  // XXX Should these be called regardless of whether the state changed?
336  GetBodyA()->SetAwake();
337  GetBodyB()->SetAwake();
338  }
339 }
340 
342 {
343  if (m_motorSpeed != speed)
344  {
345  m_motorSpeed = speed;
346 
347  // XXX Should these be called regardless of whether the state changed?
348  GetBodyA()->SetAwake();
349  GetBodyB()->SetAwake();
350  }
351 }
352 
354 {
355  if (m_maxMotorTorque != torque)
356  {
357  m_maxMotorTorque = torque;
358 
359  // XXX Should these be called regardless of whether the state changed?
360  GetBodyA()->SetAwake();
361  GetBodyB()->SetAwake();
362  }
363 }
364 
365 Length GetJointTranslation(const WheelJoint& joint) noexcept
366 {
367  const auto pA = GetWorldPoint(*joint.GetBodyA(), joint.GetLocalAnchorA());
368  const auto pB = GetWorldPoint(*joint.GetBodyB(), joint.GetLocalAnchorB());
369  const auto d = pB - pA;
370  const auto axis = GetWorldVector(*joint.GetBodyA(), joint.GetLocalAxisA());
371  return Length{Dot(d, axis)};
372 }
373 
375 {
376  return joint.GetBodyB()->GetVelocity().angular - joint.GetBodyA()->GetVelocity().angular;
377 }
378 
379 } // namespace d2
380 } // namespace playrho