Body.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 
30 
31 #include <iterator>
32 #include <type_traits>
33 #include <utility>
34 
35 namespace playrho {
36 namespace d2 {
37 
39 {
40  // @invariant Only bodies that allow sleeping, can be put to sleep.
41  // @invariant Only "speedable" bodies can be awake.
42  // @invariant Only "speedable" bodies can have non-zero velocities.
43  // @invariant Only "accelerable" bodies can have non-zero accelerations.
44  // @invariant Only "accelerable" bodies can have non-zero "under-active" times.
45 
46  auto flags = GetFlags(bd.type);
47  if (bd.bullet)
48  {
49  flags |= e_impenetrableFlag;
50  }
51  if (bd.fixedRotation)
52  {
53  flags |= e_fixedRotationFlag;
54  }
55  if (bd.allowSleep)
56  {
57  flags |= e_autoSleepFlag;
58  }
59  if (bd.awake)
60  {
61  if ((flags & e_velocityFlag) != 0)
62  {
63  flags |= e_awakeFlag;
64  }
65  }
66  else
67  {
68  if (!bd.allowSleep && ((flags & e_velocityFlag) != 0))
69  {
70  flags |= e_awakeFlag;
71  }
72  }
73  if (bd.enabled)
74  {
75  flags |= e_enabledFlag;
76  }
77  return flags;
78 }
79 
80 Body::Body(World* world, const BodyConf& bd):
81  m_xf{bd.location, UnitVec::Get(bd.angle)},
82  m_sweep{Position{bd.location, bd.angle}},
83  m_flags{GetFlags(bd)},
84  m_world{world},
85  m_userData{bd.userData},
86  m_invMass{(bd.type == playrho::BodyType::Dynamic)? InvMass{Real{1} / Kilogram}: InvMass{0}},
87  m_linearDamping{bd.linearDamping},
88  m_angularDamping{bd.angularDamping}
89 {
90  assert(IsValid(bd.location));
91  assert(IsValid(bd.angle));
92  assert(IsValid(bd.linearVelocity));
93  assert(IsValid(bd.angularVelocity));
94  assert(IsValid(m_xf));
95 
96  SetVelocity(Velocity{bd.linearVelocity, bd.angularVelocity});
97  SetAcceleration(bd.linearAcceleration, bd.angularAcceleration);
98  SetUnderActiveTime(bd.underActiveTime);
99 }
100 
101 Body::~Body() noexcept
102 {
103  assert(empty(m_joints));
104  assert(empty(m_contacts));
105  assert(empty(m_fixtures));
106 }
107 
109 {
110  WorldAtty::SetType(*m_world, *this, type);
111 }
112 
113 Fixture* Body::CreateFixture(const Shape& shape, const FixtureConf& def,
114  bool resetMassData)
115 {
116  return WorldAtty::CreateFixture(*m_world, *this, shape, def, resetMassData);
117 }
118 
119 bool Body::Destroy(Fixture* fixture, bool resetMassData)
120 {
121  if (fixture->GetBody() != this)
122  {
123  return false;
124  }
125  return WorldAtty::Destroy(*m_world, *fixture, resetMassData);
126 }
127 
129 {
130  while (!empty(m_fixtures))
131  {
132  const auto fixture = GetPtr(m_fixtures.front());
133  Destroy(fixture, false);
134  }
135  ResetMassData();
136 }
137 
139 {
140  // Compute mass data from shapes. Each shape has its own density.
141 
142  // Non-dynamic bodies (Static and kinematic ones) have zero mass.
143  if (!IsAccelerable())
144  {
145  m_invMass = 0;
146  m_invRotI = 0;
147  m_sweep = Sweep{Position{GetLocation(), GetAngle()}};
148  UnsetMassDataDirty();
149  return;
150  }
151 
152  const auto massData = ComputeMassData(*this);
153 
154  // Force all dynamic bodies to have a positive mass.
155  const auto mass = (massData.mass > 0_kg)? Mass{massData.mass}: 1_kg;
156  m_invMass = Real{1} / mass;
157 
158  // Compute center of mass.
159  const auto localCenter = massData.center * Real{m_invMass * Kilogram};
160 
161  if ((massData.I > RotInertia{0}) && (!IsFixedRotation()))
162  {
163  // Center the inertia about the center of mass.
164  const auto lengthSquared = GetMagnitudeSquared(localCenter);
165  const auto I = RotInertia{massData.I} - RotInertia{(mass * lengthSquared / SquareRadian)};
166  //assert((massData.I - mass * lengthSquared) > 0);
167  m_invRotI = Real{1} / I;
168  }
169  else
170  {
171  m_invRotI = 0;
172  }
173 
174  // Move center of mass.
175  const auto oldCenter = GetWorldCenter();
176  m_sweep = Sweep{Position{Transform(localCenter, GetTransformation()), GetAngle()}, localCenter};
177  const auto newCenter = GetWorldCenter();
178 
179  // Update center of mass velocity.
180  const auto deltaCenter = newCenter - oldCenter;
181  m_velocity.linear += GetRevPerpendicular(deltaCenter) * (m_velocity.angular / Radian);
182 
183  UnsetMassDataDirty();
184 }
185 
186 void Body::SetMassData(const MassData& massData)
187 {
188  if (m_world->IsLocked())
189  {
190  throw WrongState("Body::SetMassData: world is locked");
191  }
192 
193  if (!IsAccelerable())
194  {
195  return;
196  }
197 
198  const auto mass = (massData.mass > 0_kg)? Mass{massData.mass}: 1_kg;
199  m_invMass = Real{1} / mass;
200 
201  if ((massData.I > RotInertia{0}) && (!IsFixedRotation()))
202  {
203  const auto lengthSquared = GetMagnitudeSquared(massData.center);
204  // L^2 M QP^-2
205  const auto I = RotInertia{massData.I} - RotInertia{(mass * lengthSquared) / SquareRadian};
206  assert(I > RotInertia{0});
207  m_invRotI = Real{1} / I;
208  }
209  else
210  {
211  m_invRotI = 0;
212  }
213 
214  // Move center of mass.
215  const auto oldCenter = GetWorldCenter();
216  m_sweep = Sweep{
218  massData.center
219  };
220 
221  // Update center of mass velocity.
222  const auto newCenter = GetWorldCenter();
223  const auto deltaCenter = newCenter - oldCenter;
224  m_velocity.linear += GetRevPerpendicular(deltaCenter) * (m_velocity.angular / Radian);
225 
226  UnsetMassDataDirty();
227 }
228 
229 void Body::SetVelocity(const Velocity& velocity) noexcept
230 {
231  if ((velocity.linear != LinearVelocity2{}) || (velocity.angular != 0_rpm))
232  {
233  if (!IsSpeedable())
234  {
235  return;
236  }
237  SetAwakeFlag();
238  ResetUnderActiveTime();
239  }
240  m_velocity = velocity;
241 }
242 
244 {
245  assert(IsValid(linear));
246  assert(IsValid(angular));
247 
248  if ((m_linearAcceleration == linear) && (m_angularAcceleration == angular))
249  {
250  // no change, bail...
251  return;
252  }
253 
254  if (!IsAccelerable())
255  {
256  if ((linear != LinearAcceleration2{}) || (angular != AngularAcceleration{0}))
257  {
258  // non-accelerable bodies can only be set to zero acceleration, bail...
259  return;
260  }
261  }
262  else
263  {
264  if ((m_angularAcceleration < angular) ||
265  (GetMagnitudeSquared(m_linearAcceleration) < GetMagnitudeSquared(linear)) ||
266  (playrho::GetAngle(m_linearAcceleration) != playrho::GetAngle(linear)) ||
267  (signbit(m_angularAcceleration) != signbit(angular)))
268  {
269  // Increasing accel or changing direction of accel, awake & reset time.
270  SetAwakeFlag();
271  ResetUnderActiveTime();
272  }
273  }
274 
275  m_linearAcceleration = linear;
276  m_angularAcceleration = angular;
277 }
278 
279 void Body::SetTransformation(Transformation value) noexcept
280 {
281  assert(IsValid(value));
282  if (m_xf != value)
283  {
284  m_xf = value;
285  std::for_each(cbegin(m_contacts), cend(m_contacts), [&](KeyedContactPtr ci) {
286  std::get<Contact*>(ci)->FlagForUpdating();
287  });
288  }
289 }
290 
291 void Body::SetTransform(Length2 location, Angle angle)
292 {
293  assert(IsValid(location));
294  assert(IsValid(angle));
295 
296  if (GetWorld()->IsLocked())
297  {
298  throw WrongState("Body::SetTransform: world is locked");
299  }
300 
301  const auto xfm = Transformation{location, UnitVec::Get(angle)};
302  SetTransformation(xfm);
303 
304  m_sweep = Sweep{Position{Transform(GetLocalCenter(), xfm), angle}, GetLocalCenter()};
305 
306  WorldAtty::RegisterForProxies(*GetWorld(), *this);
307 }
308 
309 void Body::SetEnabled(bool flag)
310 {
311  if (IsEnabled() == flag)
312  {
313  return;
314  }
315 
316  if (m_world->IsLocked())
317  {
318  throw WrongState("Body::SetEnabled: world is locked");
319  }
320 
321  if (flag)
322  {
323  SetEnabledFlag();
324  }
325  else
326  {
327  UnsetEnabledFlag();
328  }
329 
330  // Register for proxies so contacts created or destroyed the next time step.
331  std::for_each(begin(m_fixtures), end(m_fixtures), [&](Fixtures::value_type &f) {
332  WorldAtty::RegisterForProxies(*m_world, GetRef(f));
333  });
334 }
335 
336 void Body::SetFixedRotation(bool flag)
337 {
338  const auto status = IsFixedRotation();
339  if (status == flag)
340  {
341  return;
342  }
343 
344  if (flag)
345  {
346  m_flags |= e_fixedRotationFlag;
347  }
348  else
349  {
350  m_flags &= ~e_fixedRotationFlag;
351  }
352 
353  m_velocity.angular = 0_rpm;
354 
355  ResetMassData();
356 }
357 
358 bool Body::Insert(Joint* joint)
359 {
360  const auto bodyA = joint->GetBodyA();
361  const auto bodyB = joint->GetBodyB();
362 
363  const auto other = (this == bodyA)? bodyB: (this == bodyB)? bodyA: nullptr;
364  m_joints.push_back(std::make_pair(other, joint));
365  return true;
366 }
367 
368 bool Body::Insert(ContactKey key, Contact* contact)
369 {
370 #ifndef NDEBUG
371  // Prevent the same contact from being added more than once...
372  const auto it = std::find_if(cbegin(m_contacts), cend(m_contacts), [&](KeyedContactPtr ci) {
373  return std::get<Contact*>(ci) == contact;
374  });
375  assert(it == end(m_contacts));
376  if (it != end(m_contacts))
377  {
378  return false;
379  }
380 #endif
381 
382  m_contacts.emplace_back(key, contact);
383  return true;
384 }
385 
386 bool Body::Erase(const Joint* joint)
387 {
388  const auto it = std::find_if(begin(m_joints), end(m_joints), [&](KeyedJointPtr ji) {
389  return std::get<Joint*>(ji) == joint;
390  });
391  if (it != end(m_joints))
392  {
393  m_joints.erase(it);
394  return true;
395  }
396  return false;
397 }
398 
399 bool Body::Erase(const Contact* contact)
400 {
401  const auto it = std::find_if(begin(m_contacts), end(m_contacts), [&](KeyedContactPtr ci) {
402  return std::get<Contact*>(ci) == contact;
403  });
404  if (it != end(m_contacts))
405  {
406  m_contacts.erase(it);
407  return true;
408  }
409  return false;
410 }
411 
412 void Body::ClearContacts()
413 {
414  m_contacts.clear();
415 }
416 
417 void Body::ClearJoints()
418 {
419  m_joints.clear();
420 }
421 
422 // Free functions...
423 
424 bool ShouldCollide(const Body& lhs, const Body& rhs) noexcept
425 {
426  // At least one body should be accelerable/dynamic.
427  if (!lhs.IsAccelerable() && !rhs.IsAccelerable())
428  {
429  return false;
430  }
431 
432  // Does a joint prevent collision?
433  const auto joints = lhs.GetJoints();
434  const auto it = std::find_if(cbegin(joints), cend(joints), [&](Body::KeyedJointPtr ji) {
435  return (std::get<0>(ji) == &rhs) && !(std::get<Joint*>(ji)->GetCollideConnected());
436  });
437  return it == end(joints);
438 }
439 
440 BodyCounter GetWorldIndex(const Body* body) noexcept
441 {
442  if (body)
443  {
444  const auto world = body->GetWorld();
445  const auto bodies = world->GetBodies();
446  auto i = BodyCounter{0};
447  const auto it = std::find_if(cbegin(bodies), cend(bodies), [&](const Body *b) {
448  return b == body || ((void) ++i, false);
449  });
450  if (it != end(bodies))
451  {
452  return i;
453  }
454  }
455  return BodyCounter(-1);
456 }
457 
458 Velocity GetVelocity(const Body& body, Time h) noexcept
459 {
460  // Integrate velocity and apply damping.
461  auto velocity = body.GetVelocity();
462  if (body.IsAccelerable())
463  {
464  // Integrate velocities.
465  velocity.linear += h * body.GetLinearAcceleration();
466  velocity.angular += h * body.GetAngularAcceleration();
467 
468  // Apply damping.
469  // Ordinary differential equation: dv/dt + c * v = 0
470  // Solution: v(t) = v0 * exp(-c * t)
471  // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt)
472  // v2 = exp(-c * dt) * v1
473  // Pade approximation (see https://en.wikipedia.org/wiki/Pad%C3%A9_approximant ):
474  // v2 = v1 * 1 / (1 + c * dt)
475  velocity.linear /= Real{1 + h * body.GetLinearDamping()};
476  velocity.angular /= Real{1 + h * body.GetAngularDamping()};
477  }
478 
479  return velocity;
480 }
481 
482 Velocity Cap(Velocity velocity, Time h, MovementConf conf) noexcept
483 {
484  const auto translation = h * velocity.linear;
485  const auto lsquared = GetMagnitudeSquared(translation);
486  if (lsquared > Square(conf.maxTranslation))
487  {
488  // Scale back linear velocity so max translation not exceeded.
489  const auto ratio = conf.maxTranslation / sqrt(lsquared);
490  velocity.linear *= ratio;
491  }
492 
493  const auto absRotation = abs(h * velocity.angular);
494  if (absRotation > conf.maxRotation)
495  {
496  // Scale back angular velocity so max rotation not exceeded.
497  const auto ratio = conf.maxRotation / absRotation;
498  velocity.angular *= ratio;
499  }
500 
501  return velocity;
502 }
503 
504 std::size_t GetFixtureCount(const Body& body) noexcept
505 {
506  const auto& fixtures = body.GetFixtures();
507  return size(fixtures);
508 }
509 
510 void RotateAboutWorldPoint(Body& body, Angle amount, Length2 worldPoint)
511 {
512  const auto xfm = body.GetTransformation();
513  const auto p = xfm.p - worldPoint;
514  const auto c = cos(amount);
515  const auto s = sin(amount);
516  const auto x = GetX(p) * c - GetY(p) * s;
517  const auto y = GetX(p) * s + GetY(p) * c;
518  const auto pos = Length2{x, y} + worldPoint;
519  const auto angle = GetAngle(xfm.q) + amount;
520  body.SetTransform(pos, angle);
521 }
522 
523 void RotateAboutLocalPoint(Body& body, Angle amount, Length2 localPoint)
524 {
525  RotateAboutWorldPoint(body, amount, GetWorldPoint(body, localPoint));
526 }
527 
529 {
530  // For background on centripetal force, see:
531  // https://en.wikipedia.org/wiki/Centripetal_force
532 
533  // Force is M L T^-2.
534  const auto velocity = GetLinearVelocity(body);
535  const auto magnitudeOfVelocity = GetMagnitude(GetVec2(velocity)) * MeterPerSecond;
536  const auto location = body.GetLocation();
537  const auto mass = GetMass(body);
538  const auto delta = axis - location;
539  const auto invRadius = Real{1} / GetMagnitude(delta);
540  const auto dir = delta * invRadius;
541  return Force2{dir * mass * Square(magnitudeOfVelocity) * invRadius};
542 }
543 
545 {
546  const auto m1 = GetMass(body);
547  if (m1 != 0_kg)
548  {
549  const auto loc1 = GetLocation(body);
550  auto sumForce = Force2{};
551  const auto world = body.GetWorld();
552  const auto bodies = world->GetBodies();
553  for (auto jt = begin(bodies); jt != end(bodies); jt = std::next(jt))
554  {
555  const auto& b2 = *(*jt);
556  if (&b2 == &body)
557  {
558  continue;
559  }
560  const auto m2 = GetMass(b2);
561  const auto delta = GetLocation(b2) - loc1;
562  const auto dir = GetUnitVector(delta);
563  const auto rr = GetMagnitudeSquared(delta);
564 
565  // Uses Newton's law of universal gravitation: F = G * m1 * m2 / rr.
566  // See: https://en.wikipedia.org/wiki/Newton%27s_law_of_universal_gravitation
567  // Note that BigG is typically very small numerically compared to either mass
568  // or the square of the radius between the masses. That's important to recognize
569  // in order to avoid operational underflows or overflows especially when
570  // playrho::Real has less exponential range like when it's defined to be float
571  // instead of double. The operational ordering is deliberately established here
572  // to help with this.
573  const auto orderedMass = std::minmax(m1, m2);
574  const auto f = (BigG * std::get<0>(orderedMass)) * (std::get<1>(orderedMass) / rr);
575  sumForce += f * dir;
576  }
577  // F = m a... i.e. a = F / m.
578  return Acceleration{sumForce / m1, 0 * RadianPerSquareSecond};
579  }
580  return Acceleration{};
581 }
582 
583 } // namespace d2
584 } // namespace playrho