RigidBody.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. //
  2. // Urho3D Engine
  3. // Copyright (c) 2008-2012 Lasse Öörni
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. //
  23. #include "Precompiled.h"
  24. #include "CollisionShape.h"
  25. #include "Context.h"
  26. #include "Log.h"
  27. #include "MemoryBuffer.h"
  28. #include "PhysicsWorld.h"
  29. #include "ResourceCache.h"
  30. #include "ResourceEvents.h"
  31. #include "RigidBody.h"
  32. #include "Scene.h"
  33. #include "XMLElement.h"
  34. #include <ode/ode.h>
  35. #include "DebugNew.h"
  36. static const float DEFAULT_MASS = 1.0f;
  37. static const int DEFAULT_MASS_AXIS = 1;
  38. static const String modeNames[] =
  39. {
  40. "static",
  41. "dynamic",
  42. "kinematic"
  43. };
  44. OBJECTTYPESTATIC(RigidBody);
  45. RigidBody::RigidBody(Context* context) :
  46. Component(context),
  47. mass_(DEFAULT_MASS),
  48. massAxis_(DEFAULT_MASS_AXIS),
  49. body_(0),
  50. inPostStep_(false)
  51. {
  52. }
  53. RigidBody::~RigidBody()
  54. {
  55. ReleaseBody();
  56. if (physicsWorld_)
  57. physicsWorld_->RemoveRigidBody(this);
  58. }
  59. void RigidBody::RegisterObject(Context* context)
  60. {
  61. context->RegisterFactory<RigidBody>();
  62. ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Mass", GetMass, SetMass, float, DEFAULT_MASS, AM_DEFAULT);
  63. ACCESSOR_ATTRIBUTE(RigidBody, VAR_INT, "Mass Axis", GetMassAxis, SetMassAxis, int, DEFAULT_MASS_AXIS, AM_DEFAULT);
  64. REF_ACCESSOR_ATTRIBUTE(RigidBody, VAR_VECTOR3, "Physics Position", GetPosition, SetPosition, Vector3, Vector3::ZERO, AM_FILE | AM_NOEDIT);
  65. REF_ACCESSOR_ATTRIBUTE(RigidBody, VAR_QUATERNION, "Physics Rotation", GetRotation, SetRotation, Quaternion, Quaternion::IDENTITY, AM_FILE | AM_NOEDIT);
  66. REF_ACCESSOR_ATTRIBUTE(RigidBody, VAR_VECTOR3, "Linear Velocity", GetLinearVelocity, SetLinearVelocity, Vector3, Vector3::ZERO, AM_DEFAULT | AM_LATESTDATA);
  67. REF_ACCESSOR_ATTRIBUTE(RigidBody, VAR_VECTOR3, "Angular Velocity", GetAngularVelocity, SetAngularVelocity, Vector3, Vector3::ZERO, AM_FILE);
  68. ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Lin Rest Threshold", GetLinearRestThreshold, SetLinearRestThreshold, float, 0.01f, AM_DEFAULT);
  69. ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Lin Damp Threshold", GetLinearDampingThreshold, SetLinearDampingThreshold, float, 0.01f, AM_DEFAULT);
  70. ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Lin Damp Scale", GetLinearDampingScale, SetLinearDampingScale, float, 0.0f, AM_DEFAULT);
  71. ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Ang Rest Threshold", GetAngularRestThreshold, SetAngularRestThreshold, float, 0.01f, AM_DEFAULT);
  72. ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Ang Damp Threshold", GetAngularDampingThreshold, SetAngularDampingThreshold, float, 0.01f, AM_DEFAULT);
  73. ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Ang Damp Scale", GetAngularDampingScale, SetAngularDampingScale, float, 0.0f, AM_DEFAULT);
  74. ACCESSOR_ATTRIBUTE(RigidBody, VAR_FLOAT, "Ang Max Velocity", GetAngularMaxVelocity, SetAngularMaxVelocity, float, M_INFINITY, AM_DEFAULT);
  75. REF_ACCESSOR_ATTRIBUTE(RigidBody, VAR_BUFFER, "Network Ang Velocity", GetNetAngularVelocityAttr, SetNetAngularVelocityAttr, PODVector<unsigned char>, PODVector<unsigned char>(), AM_NET | AM_LATESTDATA | AM_NOEDIT);
  76. ACCESSOR_ATTRIBUTE(RigidBody, VAR_BOOL, "Is Active", IsActive, SetActive, bool, true, AM_FILE);
  77. }
  78. void RigidBody::SetMass(float mass)
  79. {
  80. mass_ = Max(mass, 0.0f);
  81. UpdateMass();
  82. }
  83. void RigidBody::SetMassAxis(int massAxis)
  84. {
  85. massAxis_ = Clamp(massAxis, 0, 2);
  86. UpdateMass();
  87. }
  88. void RigidBody::SetPosition(const Vector3& position)
  89. {
  90. if (body_)
  91. {
  92. dBodySetPosition(body_, position.x_, position.y_, position.z_);
  93. previousPosition_ = position;
  94. }
  95. }
  96. void RigidBody::SetRotation(const Quaternion& rotation)
  97. {
  98. if (body_)
  99. {
  100. dBodySetQuaternion(body_, rotation.Data());
  101. previousRotation_ = rotation;
  102. }
  103. }
  104. void RigidBody::SetTransform(const Vector3& position, const Quaternion& rotation)
  105. {
  106. if (body_)
  107. {
  108. dBodySetPosition(body_, position.x_, position.y_, position.z_);
  109. dBodySetQuaternion(body_, rotation.Data());
  110. previousPosition_ = position;
  111. previousRotation_ = rotation;
  112. }
  113. }
  114. void RigidBody::SetLinearVelocity(const Vector3& velocity)
  115. {
  116. if (body_)
  117. dBodySetLinearVel(body_, velocity.x_, velocity.y_, velocity.z_);
  118. }
  119. void RigidBody::SetLinearRestThreshold(float threshold)
  120. {
  121. if (body_)
  122. dBodySetAutoDisableLinearThreshold(body_, threshold);
  123. }
  124. void RigidBody::SetLinearDampingThreshold(float threshold)
  125. {
  126. if (body_)
  127. dBodySetLinearDampingThreshold(body_, threshold);
  128. }
  129. void RigidBody::SetLinearDampingScale(float scale)
  130. {
  131. if (body_)
  132. dBodySetLinearDamping(body_, scale);
  133. }
  134. void RigidBody::SetAngularVelocity(const Vector3& velocity)
  135. {
  136. if (body_)
  137. dBodySetAngularVel(body_, velocity.x_, velocity.y_, velocity.z_);
  138. }
  139. void RigidBody::SetAngularRestThreshold(float threshold)
  140. {
  141. if (body_)
  142. dBodySetAutoDisableAngularThreshold(body_, threshold);
  143. }
  144. void RigidBody::SetAngularDampingThreshold(float threshold)
  145. {
  146. if (body_)
  147. dBodySetAngularDampingThreshold(body_, threshold);
  148. }
  149. void RigidBody::SetAngularDampingScale(float scale)
  150. {
  151. if (body_)
  152. dBodySetAngularDamping(body_, scale);
  153. }
  154. void RigidBody::SetAngularMaxVelocity(float velocity)
  155. {
  156. if (body_)
  157. dBodySetMaxAngularSpeed(body_, velocity);
  158. }
  159. void RigidBody::SetActive(bool active)
  160. {
  161. if (body_)
  162. {
  163. if (active && !dBodyIsEnabled(body_))
  164. dBodyEnable(body_);
  165. else if (!active && dBodyIsEnabled(body_))
  166. dBodyDisable(body_);
  167. }
  168. }
  169. void RigidBody::ApplyForce(const Vector3& force)
  170. {
  171. if (force == Vector3::ZERO)
  172. return;
  173. if (body_)
  174. {
  175. SetActive(true);
  176. dBodyAddForce(body_, force.x_, force.y_, force.z_);
  177. }
  178. }
  179. void RigidBody::ApplyForceAtPosition(const Vector3& force, const Vector3& position)
  180. {
  181. if (force == Vector3::ZERO)
  182. return;
  183. if (body_)
  184. {
  185. SetActive(true);
  186. dBodyAddForceAtRelPos(body_, force.x_, force.y_, force.z_, position.x_, position.y_, position.z_);
  187. }
  188. }
  189. void RigidBody::ApplyTorque(const Vector3& torque)
  190. {
  191. if (torque == Vector3::ZERO)
  192. return;
  193. if (body_)
  194. {
  195. SetActive(true);
  196. dBodyAddTorque(body_, torque.x_, torque.y_, torque.z_);
  197. }
  198. }
  199. void RigidBody::ResetForces()
  200. {
  201. if (body_)
  202. {
  203. dBodySetForce(body_, 0.0f, 0.0f, 0.0f);
  204. dBodySetTorque(body_, 0.0f, 0.0f, 0.0f);
  205. }
  206. }
  207. const Vector3& RigidBody::GetPosition() const
  208. {
  209. if (body_)
  210. return *reinterpret_cast<const Vector3*>(dBodyGetPosition(body_));
  211. else
  212. return Vector3::ZERO;
  213. }
  214. const Quaternion& RigidBody::GetRotation() const
  215. {
  216. if (body_)
  217. return *reinterpret_cast<const Quaternion*>(dBodyGetQuaternion(body_));
  218. else
  219. return Quaternion::IDENTITY;
  220. }
  221. const Vector3& RigidBody::GetLinearVelocity() const
  222. {
  223. if (body_)
  224. return *reinterpret_cast<const Vector3*>(dBodyGetLinearVel(body_));
  225. else
  226. return Vector3::ZERO;
  227. }
  228. float RigidBody::GetLinearRestThreshold() const
  229. {
  230. if (body_)
  231. return dBodyGetAutoDisableLinearThreshold(body_);
  232. else
  233. return 0.0f;
  234. }
  235. float RigidBody::GetLinearDampingThreshold() const
  236. {
  237. if (body_)
  238. return dBodyGetLinearDampingThreshold(body_);
  239. else
  240. return 0.0f;
  241. }
  242. float RigidBody::GetLinearDampingScale() const
  243. {
  244. if (body_)
  245. return dBodyGetLinearDamping(body_);
  246. else
  247. return 0.0f;
  248. }
  249. const Vector3& RigidBody::GetAngularVelocity() const
  250. {
  251. if (body_)
  252. return *reinterpret_cast<const Vector3*>(dBodyGetAngularVel(body_));
  253. else
  254. return Vector3::ZERO;
  255. }
  256. float RigidBody::GetAngularRestThreshold() const
  257. {
  258. if (body_)
  259. return dBodyGetAutoDisableAngularThreshold(body_);
  260. else
  261. return 0.0f;
  262. }
  263. float RigidBody::GetAngularDampingThreshold() const
  264. {
  265. if (body_)
  266. return dBodyGetAngularDampingThreshold(body_);
  267. else
  268. return 0.0f;
  269. }
  270. float RigidBody::GetAngularDampingScale() const
  271. {
  272. if (body_)
  273. return dBodyGetAngularDamping(body_);
  274. else
  275. return 0.0f;
  276. }
  277. float RigidBody::GetAngularMaxVelocity() const
  278. {
  279. if (body_)
  280. return dBodyGetMaxAngularSpeed(body_);
  281. else
  282. return 0.0f;
  283. }
  284. bool RigidBody::IsActive() const
  285. {
  286. if (body_)
  287. return dBodyIsEnabled(body_) != 0;
  288. else
  289. return false;
  290. }
  291. void RigidBody::SetNetAngularVelocityAttr(const PODVector<unsigned char>& value)
  292. {
  293. float maxVelocity = physicsWorld_ ? physicsWorld_->GetMaxNetworkAngularVelocity() : DEFAULT_MAX_NETWORK_ANGULAR_VELOCITY;
  294. MemoryBuffer buf(value);
  295. SetAngularVelocity(buf.ReadPackedVector3(maxVelocity));
  296. }
  297. const PODVector<unsigned char>& RigidBody::GetNetAngularVelocityAttr() const
  298. {
  299. float maxVelocity = physicsWorld_ ? physicsWorld_->GetMaxNetworkAngularVelocity() : DEFAULT_MAX_NETWORK_ANGULAR_VELOCITY;
  300. attrBuffer_.Clear();
  301. attrBuffer_.WritePackedVector3(GetAngularVelocity(), maxVelocity);
  302. return attrBuffer_.GetBuffer();
  303. }
  304. void RigidBody::OnMarkedDirty(Node* node)
  305. {
  306. // Physics operations are not safe from worker threads
  307. Scene* scene = node->GetScene();
  308. if (scene && scene->IsThreadedUpdate())
  309. {
  310. scene->DelayedMarkedDirty(this);
  311. return;
  312. }
  313. // If the node is smoothed, do not use the dirty callback, but rather update manually during prestep
  314. if (node_->GetSmoothing())
  315. return;
  316. // Clear the dirty flag by querying world position; this way we are sure to get the dirty notification immediately
  317. // also the next time the node transform changes
  318. Vector3 newPosition = node_->GetWorldPosition();
  319. // Disregard node dirtying during the physics poststep, when we set node transform ourselves
  320. if (inPostStep_ || !body_)
  321. return;
  322. Quaternion newRotation(node_->GetWorldRotation());
  323. // Check that the node actually moved or rotated from the last transform set during poststep.
  324. // If only rotated, do not force the physics position, and vice versa.
  325. if (newPosition != lastInterpolatedPosition_)
  326. {
  327. SetActive(true);
  328. dBodySetPosition(body_, newPosition.x_, newPosition.y_, newPosition.z_);
  329. lastInterpolatedPosition_ = previousPosition_ = newPosition;
  330. }
  331. if (newRotation != lastInterpolatedRotation_)
  332. {
  333. SetActive(true);
  334. dBodySetQuaternion(body_, newRotation.Data());
  335. lastInterpolatedRotation_ = previousRotation_ = newRotation;
  336. }
  337. }
  338. void RigidBody::OnNodeSet(Node* node)
  339. {
  340. if (node)
  341. {
  342. Scene* scene = node->GetScene();
  343. if (scene)
  344. {
  345. physicsWorld_ = scene->GetComponent<PhysicsWorld>();
  346. if (physicsWorld_)
  347. physicsWorld_->AddRigidBody(this);
  348. CreateBody();
  349. }
  350. node->AddListener(this);
  351. }
  352. }
  353. void RigidBody::PreStep()
  354. {
  355. if (!body_)
  356. return;
  357. const Vector3& currentPosition = *reinterpret_cast<const Vector3*>(dBodyGetPosition(body_));
  358. const Quaternion& currentRotation = *reinterpret_cast<const Quaternion*>(dBodyGetQuaternion(body_));
  359. if (!node_->GetSmoothing())
  360. {
  361. // If no smoothing, store the current body position for interpolation
  362. previousPosition_ = currentPosition;
  363. previousRotation_ = currentRotation;
  364. }
  365. else
  366. {
  367. // If smoothing is active, get the node's target transform and check manually if it has changed
  368. // (as a result of eg. server update from the network)
  369. Matrix3x4 transform = node_->GetWorldTargetTransform();
  370. Vector3 newPosition = transform.Translation();
  371. Quaternion newRotation = transform.Rotation();
  372. if (newPosition != currentPosition)
  373. {
  374. SetActive(true);
  375. dBodySetPosition(body_, newPosition.x_, newPosition.y_, newPosition.z_);
  376. }
  377. if (newRotation != currentRotation)
  378. {
  379. SetActive(true);
  380. dBodySetQuaternion(body_, newRotation.Data());
  381. }
  382. }
  383. }
  384. void RigidBody::PostStep(float t, HashSet<RigidBody*>& processedBodies)
  385. {
  386. if (!body_ || !IsActive())
  387. return;
  388. processedBodies.Insert(this);
  389. inPostStep_ = true;
  390. // If the parent node has a rigid body, process it first
  391. // Note: for optimization, we intentionally assume that the scene root node can not have a rigid body
  392. Node* parent = node_->GetParent();
  393. if (parent && parent != node_->GetScene())
  394. {
  395. RigidBody* parentBody = parent->GetComponent<RigidBody>();
  396. if (parentBody && !processedBodies.Contains(parentBody))
  397. parentBody->PostStep(t, processedBodies);
  398. }
  399. // Apply the physics transform to rendering transform now
  400. const Vector3& currentPosition = *reinterpret_cast<const Vector3*>(dBodyGetPosition(body_));
  401. const Quaternion& currentRotation = *reinterpret_cast<const Quaternion*>(dBodyGetQuaternion(body_));
  402. if (!parent)
  403. {
  404. // If node already has motion smoothing enabled, do not do substep interpolation
  405. if (!node_->GetSmoothing())
  406. node_->SetTransform(previousPosition_.Lerp(currentPosition, t), previousRotation_.Slerp(currentRotation, t));
  407. else
  408. node_->SetTransform(currentPosition, currentRotation);
  409. }
  410. else
  411. {
  412. // Transform rigid body's world coordinates back to parent's space
  413. if (!node_->GetSmoothing())
  414. {
  415. Matrix3x4 newTransform(parent->GetWorldTransform().Inverse() * Matrix3x4(previousPosition_.Lerp(currentPosition, t),
  416. previousRotation_.Slerp(currentRotation, t), Vector3::ONE));
  417. node_->SetTransform(newTransform.Translation(), newTransform.Rotation());
  418. }
  419. else
  420. {
  421. Matrix3x4 newTransform(parent->GetWorldTargetTransform().Inverse() * Matrix3x4(currentPosition, currentRotation,
  422. Vector3::ONE));
  423. node_->SetTransform(newTransform.Translation(), newTransform.Rotation());
  424. }
  425. }
  426. lastInterpolatedPosition_ = node_->GetWorldPosition();
  427. lastInterpolatedRotation_ = node_->GetWorldRotation();
  428. inPostStep_ = false;
  429. }
  430. void RigidBody::CreateBody()
  431. {
  432. if (!physicsWorld_)
  433. {
  434. LOGERROR("Null physics world, can not create body");
  435. return;
  436. }
  437. if (!body_)
  438. {
  439. body_ = dBodyCreate(physicsWorld_->GetWorld());
  440. // Set the user data pointer
  441. dBodySetData(body_, this);
  442. // Set initial transform. Use target position in case the node used smoothing
  443. const Vector3& position = node_->GetTargetPosition();
  444. Quaternion rotation(node_->GetTargetRotation());
  445. dBodySetPosition(body_, position.x_, position.y_, position.z_);
  446. dBodySetQuaternion(body_, rotation.Data());
  447. lastInterpolatedPosition_ = previousPosition_ = position;
  448. lastInterpolatedRotation_ = previousRotation_ = rotation;
  449. // Associate geometries with the body
  450. PODVector<CollisionShape*> shapes;
  451. GetComponents<CollisionShape>(shapes);
  452. for (unsigned i = 0; i < shapes.Size(); ++i)
  453. shapes[i]->UpdateTransform();
  454. }
  455. UpdateMass();
  456. }
  457. void RigidBody::ReleaseBody()
  458. {
  459. if (!body_ || !physicsWorld_)
  460. return;
  461. PODVector<CollisionShape*> shapes;
  462. GetComponents<CollisionShape>(shapes);
  463. // First remove rigid body associations
  464. for (unsigned i = 0; i < shapes.Size(); ++i)
  465. {
  466. dGeomID geom = shapes[i]->GetGeometry();
  467. if (geom)
  468. dGeomSetBody(geom, 0);
  469. }
  470. dBodyDestroy(body_);
  471. body_ = 0;
  472. // Then update geometry transforms
  473. for (unsigned i = 0; i < shapes.Size(); ++i)
  474. shapes[i]->UpdateTransform();
  475. }
  476. void RigidBody::UpdateMass()
  477. {
  478. if (!body_)
  479. return;
  480. dMass mass;
  481. dMassSetZero(&mass);
  482. // Get all attached collision shapes to calculate the mass
  483. PODVector<CollisionShape*> shapes;
  484. GetComponents<CollisionShape>(shapes);
  485. float density = 1.0f;
  486. for (unsigned i = 0; i < shapes.Size(); ++i)
  487. {
  488. CollisionShape* shape = shapes[i];
  489. dMass subMass;
  490. Vector3 size = shape->GetSize() * node_->GetScale();
  491. Vector3 offset = shape->GetPosition() * node_->GetScale();
  492. switch (shape->GetShapeType())
  493. {
  494. case SHAPE_BOX:
  495. dMassSetBox(&subMass, density, size.x_, size.y_, size.z_);
  496. break;
  497. case SHAPE_SPHERE:
  498. dMassSetSphere(&subMass, density, 0.5f * size.x_);
  499. break;
  500. case SHAPE_CYLINDER:
  501. dMassSetCylinder(&subMass, density, massAxis_, 0.5f * size.x_, size.y_);
  502. break;
  503. case SHAPE_CAPSULE:
  504. dMassSetCapsule(&subMass, density, massAxis_, 0.5f * size.x_, Max(size.y_ - size.x_, 0.0f));
  505. break;
  506. case SHAPE_TRIANGLEMESH:
  507. dMassSetBox(&subMass, density, size.x_, size.y_, size.z_);
  508. break;
  509. }
  510. dMatrix3 rotMatrix;
  511. dRfromQ(rotMatrix, shape->GetRotation().Data());
  512. dMassTranslate(&subMass, offset.x_, offset.y_, offset.z_);
  513. dMassRotate(&subMass, rotMatrix);
  514. dMassAdd(&mass, &subMass);
  515. }
  516. // If zero mass or no geometries, set kinematic mode
  517. if (mass.mass <= 0.0f)
  518. dBodySetKinematic(body_);
  519. else
  520. {
  521. // Translate final mass to center; anything else is unsupported in ODE
  522. dMassTranslate(&mass, -mass.c[0], -mass.c[1], -mass.c[2]);
  523. dMassAdjust(&mass, mass_);
  524. dBodySetMass(body_, &mass);
  525. }
  526. }