MotorJoint.cpp
Go to the documentation of this file.
1 /*
2  * Original work Copyright (c) 2006-2012 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 
27 
28 namespace playrho {
29 namespace d2 {
30 
31 // Point-to-point constraint
32 // Cdot = v2 - v1
33 // = v2 + cross(w2, r2) - v1 - cross(w1, r1)
34 // J = [-I -r1_skew I r2_skew ]
35 // Identity used:
36 // w k % (rx i + ry j) = w * (-ry i + rx j)
37 //
38 // r1 = offset - c1
39 // r2 = -c2
40 
41 // Angle constraint
42 // Cdot = w2 - w1
43 // J = [0 0 -1 0 0 1]
44 // K = invI1 + invI2
45 
47  Joint(def),
48  m_linearOffset(def.linearOffset),
49  m_angularOffset(def.angularOffset),
50  m_maxForce(def.maxForce),
51  m_maxTorque(def.maxTorque),
52  m_correctionFactor(def.correctionFactor)
53 {
54  // Intentionally empty.
55 }
56 
57 void MotorJoint::Accept(JointVisitor& visitor) const
58 {
59  visitor.Visit(*this);
60 }
61 
63 {
64  visitor.Visit(*this);
65 }
66 
67 void MotorJoint::InitVelocityConstraints(BodyConstraintsMap& bodies, const StepConf& step, const ConstraintSolverConf&)
68 {
69  auto& bodyConstraintA = At(bodies, GetBodyA());
70  auto& bodyConstraintB = At(bodies, GetBodyB());
71 
72  const auto posA = bodyConstraintA->GetPosition();
73  auto velA = bodyConstraintA->GetVelocity();
74 
75  const auto posB = bodyConstraintB->GetPosition();
76  auto velB = bodyConstraintB->GetVelocity();
77 
78  const auto qA = UnitVec::Get(posA.angular);
79  const auto qB = UnitVec::Get(posB.angular);
80 
81  // Compute the effective mass matrix.
82  m_rA = Rotate(m_linearOffset - bodyConstraintA->GetLocalCenter(), qA);
83  m_rB = Rotate(-bodyConstraintB->GetLocalCenter(), qB);
84 
85  // J = [-I -r1_skew I r2_skew]
86  // r_skew = [-ry; rx]
87 
88  // Matlab
89  // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB]
90  // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB]
91  // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB]
92 
93  const auto invMassA = bodyConstraintA->GetInvMass();
94  const auto invMassB = bodyConstraintB->GetInvMass();
95  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
96  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
97 
98  {
99  const auto exx = InvMass{
100  invMassA + invMassB +
101  invRotInertiaA * Square(GetY(m_rA)) / SquareRadian +
102  invRotInertiaB * Square(GetY(m_rB)) / SquareRadian
103  };
104  const auto exy = InvMass{
105  -invRotInertiaA * GetX(m_rA) * GetY(m_rA) / SquareRadian +
106  -invRotInertiaB * GetX(m_rB) * GetY(m_rB) / SquareRadian
107  };
108  const auto eyy = InvMass{
109  invMassA + invMassB +
110  invRotInertiaA * Square(GetX(m_rA)) / SquareRadian +
111  invRotInertiaB * Square(GetX(m_rB)) / SquareRadian
112  };
113  // Upper 2 by 2 of K above for point to point
114  const auto k22 = InvMass22{Vector<InvMass, 2>{exx, exy}, Vector<InvMass, 2>{exy, eyy}};
115  m_linearMass = Invert(k22);
116  }
117 
118  const auto invRotInertia = invRotInertiaA + invRotInertiaB;
119  m_angularMass = (invRotInertia > InvRotInertia{0})? RotInertia{Real{1} / invRotInertia}: RotInertia{0};
120 
121  m_linearError = (posB.linear + m_rB) - (posA.linear + m_rA);
122  m_angularError = (posB.angular - posA.angular) - m_angularOffset;
123 
124  if (step.doWarmStart)
125  {
126  // Scale impulses to support a variable time step.
127  m_linearImpulse *= step.dtRatio;
128  m_angularImpulse *= step.dtRatio;
129 
130  const auto P = m_linearImpulse;
131  // L * M * L T^-1 / QP is: L^2 M T^-1 QP^-1 which is: AngularMomentum.
132  const auto crossAP = AngularMomentum{Cross(m_rA, P) / Radian};
133  const auto crossBP = AngularMomentum{Cross(m_rB, P) / Radian}; // L * M * L T^-1 is: L^2 M T^-1
134 
135  velA -= Velocity{invMassA * P, invRotInertiaA * (crossAP + m_angularImpulse)};
136  velB += Velocity{invMassB * P, invRotInertiaB * (crossBP + m_angularImpulse)};
137  }
138  else
139  {
140  m_linearImpulse = Momentum2{};
141  m_angularImpulse = AngularMomentum{0};
142  }
143 
144  bodyConstraintA->SetVelocity(velA);
145  bodyConstraintB->SetVelocity(velB);
146 }
147 
148 bool MotorJoint::SolveVelocityConstraints(BodyConstraintsMap& bodies, const StepConf& step)
149 {
150  auto& bodyConstraintA = At(bodies, GetBodyA());
151  auto& bodyConstraintB = At(bodies, GetBodyB());
152 
153  auto velA = bodyConstraintA->GetVelocity();
154  auto velB = bodyConstraintB->GetVelocity();
155 
156  const auto invMassA = bodyConstraintA->GetInvMass();
157  const auto invMassB = bodyConstraintB->GetInvMass();
158  const auto invRotInertiaA = bodyConstraintA->GetInvRotInertia();
159  const auto invRotInertiaB = bodyConstraintB->GetInvRotInertia();
160 
161  const auto h = step.GetTime();
162  const auto inv_h = step.GetInvTime();
163 
164  auto solved = true;
165 
166  // Solve angular friction
167  {
168  const auto Cdot = AngularVelocity{(velB.angular - velA.angular) + inv_h * m_correctionFactor * m_angularError};
169  const auto angularImpulse = AngularMomentum{-m_angularMass * Cdot};
170 
171  const auto oldAngularImpulse = m_angularImpulse;
172  const auto maxAngularImpulse = h * m_maxTorque;
173  const auto newAngularImpulse = std::clamp(oldAngularImpulse + angularImpulse,
174  -maxAngularImpulse, maxAngularImpulse);
175  m_angularImpulse = newAngularImpulse;
176  const auto incAngularImpulse = newAngularImpulse - oldAngularImpulse;
177 
178  if (incAngularImpulse != AngularMomentum{0})
179  {
180  solved = false;
181  }
182  velA.angular -= invRotInertiaA * incAngularImpulse;
183  velB.angular += invRotInertiaB * incAngularImpulse;
184  }
185 
186  // Solve linear friction
187  {
188  const auto vb = LinearVelocity2{velB.linear + (GetRevPerpendicular(m_rB) * (velB.angular / Radian))};
189  const auto va = LinearVelocity2{velA.linear + (GetRevPerpendicular(m_rA) * (velA.angular / Radian))};
190  const auto Cdot = LinearVelocity2{(vb - va) + inv_h * m_correctionFactor * m_linearError};
191 
192  const auto impulse = -Transform(Cdot, m_linearMass);
193  const auto oldImpulse = m_linearImpulse;
194  m_linearImpulse += impulse;
195 
196  const auto maxImpulse = h * m_maxForce;
197 
198  if (GetMagnitudeSquared(m_linearImpulse) > Square(maxImpulse))
199  {
200  m_linearImpulse = GetUnitVector(m_linearImpulse, UnitVec::GetZero()) * maxImpulse;
201  }
202 
203  const auto incImpulse = m_linearImpulse - oldImpulse;
204  const auto angImpulseA = AngularMomentum{Cross(m_rA, incImpulse) / Radian};
205  const auto angImpulseB = AngularMomentum{Cross(m_rB, incImpulse) / Radian};
206 
207  if (incImpulse != Momentum2{})
208  {
209  solved = false;
210  }
211 
212  velA -= Velocity{invMassA * incImpulse, invRotInertiaA * angImpulseA};
213  velB += Velocity{invMassB * incImpulse, invRotInertiaB * angImpulseB};
214  }
215 
216  bodyConstraintA->SetVelocity(velA);
217  bodyConstraintB->SetVelocity(velB);
218 
219  return solved;
220 }
221 
222 bool MotorJoint::SolvePositionConstraints(BodyConstraintsMap&, const ConstraintSolverConf&) const
223 {
224  return true;
225 }
226 
228 {
229  return GetBodyA()->GetLocation();
230 }
231 
233 {
234  return GetBodyB()->GetLocation();
235 }
236 
238 {
239  assert(IsValid(factor) && (0 <= factor) && (factor <= Real{1}));
240  m_correctionFactor = factor;
241 }
242 
243 void MotorJoint::SetLinearOffset(const Length2 linearOffset)
244 {
245  if (m_linearOffset != linearOffset)
246  {
247  m_linearOffset = linearOffset;
248  GetBodyA()->SetAwake();
249  GetBodyB()->SetAwake();
250  }
251 }
252 
254 {
255  if (angularOffset != m_angularOffset)
256  {
257  m_angularOffset = angularOffset;
258  GetBodyA()->SetAwake();
259  GetBodyB()->SetAwake();
260  }
261 }
262 
263 } // namespace d2
264 } // namespace playrho