VehicleConstraint.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <Jolt/Jolt.h>
  5. #include <Jolt/Physics/Vehicle/VehicleConstraint.h>
  6. #include <Jolt/Physics/Vehicle/VehicleController.h>
  7. #include <Jolt/Physics/PhysicsSystem.h>
  8. #include <Jolt/ObjectStream/TypeDeclarations.h>
  9. #include <Jolt/Core/StreamIn.h>
  10. #include <Jolt/Core/StreamOut.h>
  11. #include <Jolt/Core/Factory.h>
  12. JPH_NAMESPACE_BEGIN
  13. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(VehicleConstraintSettings)
  14. {
  15. JPH_ADD_BASE_CLASS(VehicleConstraintSettings, ConstraintSettings)
  16. JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mUp)
  17. JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mForward)
  18. JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mMaxPitchRollAngle)
  19. JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mWheels)
  20. JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mAntiRollBars)
  21. JPH_ADD_ATTRIBUTE(VehicleConstraintSettings, mController)
  22. }
  23. void VehicleConstraintSettings::SaveBinaryState(StreamOut &inStream) const
  24. {
  25. ConstraintSettings::SaveBinaryState(inStream);
  26. inStream.Write(mUp);
  27. inStream.Write(mForward);
  28. inStream.Write(mMaxPitchRollAngle);
  29. uint32 num_anti_rollbars = (uint32)mAntiRollBars.size();
  30. inStream.Write(num_anti_rollbars);
  31. for (const VehicleAntiRollBar &r : mAntiRollBars)
  32. r.SaveBinaryState(inStream);
  33. uint32 num_wheels = (uint32)mWheels.size();
  34. inStream.Write(num_wheels);
  35. for (const WheelSettings *w : mWheels)
  36. w->SaveBinaryState(inStream);
  37. inStream.Write(mController->GetRTTI()->GetHash());
  38. mController->SaveBinaryState(inStream);
  39. }
  40. void VehicleConstraintSettings::RestoreBinaryState(StreamIn &inStream)
  41. {
  42. ConstraintSettings::RestoreBinaryState(inStream);
  43. inStream.Read(mUp);
  44. inStream.Read(mForward);
  45. inStream.Read(mMaxPitchRollAngle);
  46. uint32 num_anti_rollbars = 0;
  47. inStream.Read(num_anti_rollbars);
  48. mAntiRollBars.resize(num_anti_rollbars);
  49. for (VehicleAntiRollBar &r : mAntiRollBars)
  50. r.RestoreBinaryState(inStream);
  51. uint32 num_wheels = 0;
  52. inStream.Read(num_wheels);
  53. mWheels.resize(num_wheels);
  54. for (WheelSettings *w : mWheels)
  55. w->RestoreBinaryState(inStream);
  56. uint32 hash = 0;
  57. inStream.Read(hash);
  58. const RTTI *rtti = Factory::sInstance->Find(hash);
  59. mController = reinterpret_cast<VehicleControllerSettings *>(rtti->CreateObject());
  60. mController->RestoreBinaryState(inStream);
  61. }
  62. VehicleConstraint::VehicleConstraint(Body &inVehicleBody, const VehicleConstraintSettings &inSettings) :
  63. Constraint(inSettings)
  64. {
  65. // Check sanity of incoming settings
  66. JPH_ASSERT(inSettings.mForward.IsNormalized());
  67. JPH_ASSERT(inSettings.mUp.IsNormalized());
  68. JPH_ASSERT(!inSettings.mWheels.empty());
  69. // Store general properties
  70. mBody = &inVehicleBody;
  71. mUp = inSettings.mUp;
  72. mForward = inSettings.mForward;
  73. SetMaxPitchRollAngle(inSettings.mMaxPitchRollAngle);
  74. // Copy anti-rollbar settings
  75. mAntiRollBars.resize(inSettings.mAntiRollBars.size());
  76. for (uint i = 0; i < mAntiRollBars.size(); ++i)
  77. {
  78. const VehicleAntiRollBar &r = inSettings.mAntiRollBars[i];
  79. mAntiRollBars[i] = r;
  80. JPH_ASSERT(r.mStiffness >= 0.0f);
  81. }
  82. // Construct our controler class
  83. mController = inSettings.mController->ConstructController(*this);
  84. // Create wheels
  85. mWheels.resize(inSettings.mWheels.size());
  86. for (uint i = 0; i < mWheels.size(); ++i)
  87. mWheels[i] = mController->ConstructWheel(*inSettings.mWheels[i]);
  88. }
  89. VehicleConstraint::~VehicleConstraint()
  90. {
  91. // Destroy controller
  92. delete mController;
  93. // Destroy our wheels
  94. for (Wheel *w : mWheels)
  95. delete w;
  96. }
  97. Mat44 VehicleConstraint::GetWheelLocalTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const
  98. {
  99. JPH_ASSERT(inWheelIndex < mWheels.size());
  100. const Wheel *wheel = mWheels[inWheelIndex];
  101. const WheelSettings *settings = wheel->mSettings;
  102. // Use the two vectors provided to calculate a matrix that takes us from wheel model space to X = right, Y = up, Z = forward (the space where we will rotate the wheel)
  103. Mat44 wheel_to_rotational = Mat44(Vec4(inWheelRight, 0), Vec4(inWheelUp, 0), Vec4(inWheelUp.Cross(inWheelRight), 0), Vec4(0, 0, 0, 1)).Transposed();
  104. // Calculate the matrix that takes us from the rotational space to vehicle local space
  105. Vec3 local_forward = Quat::sRotation(mUp, wheel->mSteerAngle) * mForward;
  106. Vec3 local_right = local_forward.Cross(mUp);
  107. Vec3 local_wheel_pos = settings->mPosition + settings->mDirection * (wheel->mContactLength - settings->mRadius);
  108. Mat44 rotational_to_local(Vec4(local_right, 0), Vec4(mUp, 0), Vec4(local_forward, 0), Vec4(local_wheel_pos, 1));
  109. // Calculate transform of rotated wheel
  110. return rotational_to_local * Mat44::sRotationX(wheel->mAngle) * wheel_to_rotational;
  111. }
  112. RMat44 VehicleConstraint::GetWheelWorldTransform(uint inWheelIndex, Vec3Arg inWheelRight, Vec3Arg inWheelUp) const
  113. {
  114. return mBody->GetWorldTransform() * GetWheelLocalTransform(inWheelIndex, inWheelRight, inWheelUp);
  115. }
  116. void VehicleConstraint::OnStep(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
  117. {
  118. JPH_PROFILE_FUNCTION();
  119. // Callback on our controller
  120. mController->PreCollide(inDeltaTime, inPhysicsSystem);
  121. // Calculate if this constraint is active by checking if our main vehicle body is active or any of the bodies we touch are active
  122. mIsActive = mBody->IsActive();
  123. // Test collision for wheels
  124. for (uint wheel_index = 0; wheel_index < mWheels.size(); ++wheel_index)
  125. {
  126. Wheel *w = mWheels[wheel_index];
  127. const WheelSettings *settings = w->mSettings;
  128. // Reset contact
  129. w->mContactBodyID = BodyID();
  130. w->mContactBody = nullptr;
  131. w->mContactSubShapeID = SubShapeID();
  132. float max_len = settings->mSuspensionMaxLength + settings->mRadius;
  133. w->mContactLength = max_len;
  134. // Test collision to find the floor
  135. RVec3 origin = mBody->GetCenterOfMassPosition() + mBody->GetRotation() * (settings->mPosition - mBody->GetShape()->GetCenterOfMass());
  136. w->mWSDirection = mBody->GetRotation() * settings->mDirection;
  137. if (mVehicleCollisionTester->Collide(inPhysicsSystem, wheel_index, origin, w->mWSDirection, max_len, mBody->GetID(), w->mContactBody, w->mContactSubShapeID, w->mContactPosition, w->mContactNormal, w->mContactLength))
  138. {
  139. // Store ID (pointer is not valid outside of the simulation step)
  140. w->mContactBodyID = w->mContactBody->GetID();
  141. // Store contact velocity, cache this as the contact body may be removed
  142. w->mContactPointVelocity = w->mContactBody->GetPointVelocity(w->mContactPosition);
  143. // Check if body is active, if so the entire vehicle should be active
  144. mIsActive |= w->mContactBody->IsActive();
  145. // Determine world space forward using steering angle and body rotation
  146. Vec3 forward = mBody->GetRotation() * Quat::sRotation(mUp, w->mSteerAngle) * mForward;
  147. // Calculate frame of reference for the contact
  148. w->mContactLateral = forward.Cross(w->mContactNormal).NormalizedOr(Vec3::sZero());
  149. w->mContactLongitudinal = w->mContactNormal.Cross(w->mContactLateral);
  150. }
  151. }
  152. // Calculate anti-rollbar impulses
  153. for (const VehicleAntiRollBar &r : mAntiRollBars)
  154. {
  155. Wheel *lw = mWheels[r.mLeftWheel];
  156. Wheel *rw = mWheels[r.mRightWheel];
  157. if (lw->mContactBody != nullptr && rw->mContactBody != nullptr)
  158. {
  159. // Calculate the impulse to apply based on the difference in suspension length
  160. float difference = rw->mContactLength - lw->mContactLength;
  161. float impulse = difference * r.mStiffness * inDeltaTime;
  162. lw->mAntiRollBarImpulse = -impulse;
  163. rw->mAntiRollBarImpulse = impulse;
  164. }
  165. else
  166. {
  167. // When one of the wheels is not on the ground we don't apply any impulses
  168. lw->mAntiRollBarImpulse = rw->mAntiRollBarImpulse = 0.0f;
  169. }
  170. }
  171. // Callback on our controller
  172. mController->PostCollide(inDeltaTime, inPhysicsSystem);
  173. // If the wheels are rotating, we don't want to go to sleep yet
  174. bool allow_sleep = mController->AllowSleep();
  175. if (allow_sleep)
  176. for (const Wheel *w : mWheels)
  177. if (abs(w->mAngularVelocity) > DegreesToRadians(10.0f))
  178. {
  179. allow_sleep = false;
  180. break;
  181. }
  182. if (mBody->GetAllowSleeping() != allow_sleep)
  183. mBody->SetAllowSleeping(allow_sleep);
  184. }
  185. void VehicleConstraint::BuildIslands(uint32 inConstraintIndex, IslandBuilder &ioBuilder, BodyManager &inBodyManager)
  186. {
  187. // Find dynamic bodies that our wheels are touching
  188. BodyID *body_ids = (BodyID *)JPH_STACK_ALLOC((mWheels.size() + 1) * sizeof(BodyID));
  189. int num_bodies = 0;
  190. bool needs_to_activate = false;
  191. for (const Wheel *w : mWheels)
  192. if (w->mContactBody != nullptr)
  193. {
  194. // Avoid adding duplicates
  195. bool duplicate = false;
  196. BodyID id = w->mContactBody->GetID();
  197. for (int i = 0; i < num_bodies; ++i)
  198. if (body_ids[i] == id)
  199. {
  200. duplicate = true;
  201. break;
  202. }
  203. if (duplicate)
  204. continue;
  205. if (w->mContactBody->IsDynamic())
  206. body_ids[num_bodies++] = id;
  207. needs_to_activate |= !w->mContactBody->IsActive();
  208. }
  209. // Activate bodies
  210. if (needs_to_activate)
  211. {
  212. if (!mBody->IsActive())
  213. {
  214. // Our main body is not active, activate it too
  215. body_ids[num_bodies] = mBody->GetID();
  216. inBodyManager.ActivateBodies(body_ids, num_bodies + 1);
  217. }
  218. else
  219. {
  220. // Only activate bodies the wheels are touching
  221. inBodyManager.ActivateBodies(body_ids, num_bodies);
  222. }
  223. }
  224. // Link the bodies into the same island
  225. uint32 min_active_index = Body::cInactiveIndex;
  226. for (int i = 0; i < num_bodies; ++i)
  227. {
  228. const Body &body = inBodyManager.GetBody(body_ids[i]);
  229. min_active_index = min(min_active_index, body.GetIndexInActiveBodiesInternal());
  230. ioBuilder.LinkBodies(mBody->GetIndexInActiveBodiesInternal(), body.GetIndexInActiveBodiesInternal());
  231. }
  232. // Link the constraint in the island
  233. ioBuilder.LinkConstraint(inConstraintIndex, mBody->GetIndexInActiveBodiesInternal(), min_active_index);
  234. }
  235. uint VehicleConstraint::BuildIslandSplits(LargeIslandSplitter &ioSplitter) const
  236. {
  237. return ioSplitter.AssignToNonParallelSplit(mBody);
  238. }
  239. void VehicleConstraint::CalculateWheelContactPoint(RMat44Arg inBodyTransform, const Wheel &inWheel, Vec3 &outR1PlusU, Vec3 &outR2) const
  240. {
  241. RVec3 contact_pos = inBodyTransform * (inWheel.mSettings->mPosition + inWheel.mSettings->mDirection * inWheel.mContactLength);
  242. outR1PlusU = Vec3(contact_pos - mBody->GetCenterOfMassPosition());
  243. outR2 = Vec3(contact_pos - inWheel.mContactBody->GetCenterOfMassPosition());
  244. }
  245. void VehicleConstraint::CalculatePitchRollConstraintProperties(float inDeltaTime, RMat44Arg inBodyTransform)
  246. {
  247. // Check if a limit was specified
  248. if (mCosMaxPitchRollAngle < JPH_PI)
  249. {
  250. // Calculate cos of angle between world up vector and vehicle up vector
  251. Vec3 vehicle_up = inBodyTransform.Multiply3x3(mUp);
  252. mCosPitchRollAngle = mUp.Dot(vehicle_up);
  253. if (mCosPitchRollAngle < mCosMaxPitchRollAngle)
  254. {
  255. // Calculate rotation axis to rotate vehicle towards up
  256. Vec3 rotation_axis = mUp.Cross(vehicle_up);
  257. float len = rotation_axis.Length();
  258. if (len > 0.0f)
  259. mPitchRollRotationAxis = rotation_axis / len;
  260. mPitchRollPart.CalculateConstraintProperties(inDeltaTime, *mBody, Body::sFixedToWorld, mPitchRollRotationAxis);
  261. }
  262. else
  263. mPitchRollPart.Deactivate();
  264. }
  265. else
  266. mPitchRollPart.Deactivate();
  267. }
  268. void VehicleConstraint::SetupVelocityConstraint(float inDeltaTime)
  269. {
  270. RMat44 body_transform = mBody->GetWorldTransform();
  271. for (Wheel *w : mWheels)
  272. if (w->mContactBody != nullptr)
  273. {
  274. const WheelSettings *settings = w->mSettings;
  275. Vec3 r1_plus_u, r2;
  276. CalculateWheelContactPoint(body_transform, *w, r1_plus_u, r2);
  277. // Suspension spring
  278. if (settings->mSuspensionMaxLength > settings->mSuspensionMinLength)
  279. w->mSuspensionPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, w->mWSDirection, w->mAntiRollBarImpulse, w->mContactLength - settings->mRadius - settings->mSuspensionMaxLength - settings->mSuspensionPreloadLength, settings->mSuspensionFrequency, settings->mSuspensionDamping);
  280. else
  281. w->mSuspensionPart.Deactivate();
  282. // Check if we reached the 'max up' position
  283. float max_up_error = w->mContactLength - settings->mRadius - settings->mSuspensionMinLength;
  284. if (max_up_error < 0.0f)
  285. w->mSuspensionMaxUpPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, w->mWSDirection, 0.0f, max_up_error);
  286. else
  287. w->mSuspensionMaxUpPart.Deactivate();
  288. // Friction and propulsion
  289. w->mLongitudinalPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, -w->mContactLongitudinal, 0.0f, 0.0f, 0.0f, 0.0f);
  290. w->mLateralPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, -w->mContactLateral, 0.0f, 0.0f, 0.0f, 0.0f);
  291. }
  292. else
  293. {
  294. // No contact -> disable everything
  295. w->mSuspensionPart.Deactivate();
  296. w->mSuspensionMaxUpPart.Deactivate();
  297. w->mLongitudinalPart.Deactivate();
  298. w->mLateralPart.Deactivate();
  299. }
  300. CalculatePitchRollConstraintProperties(inDeltaTime, body_transform);
  301. }
  302. void VehicleConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
  303. {
  304. for (Wheel *w : mWheels)
  305. if (w->mContactBody != nullptr)
  306. {
  307. w->mSuspensionPart.WarmStart(*mBody, *w->mContactBody, w->mWSDirection, inWarmStartImpulseRatio);
  308. w->mSuspensionMaxUpPart.WarmStart(*mBody, *w->mContactBody, w->mWSDirection, inWarmStartImpulseRatio);
  309. w->mLongitudinalPart.WarmStart(*mBody, *w->mContactBody, -w->mContactLongitudinal, 0.0f); // Don't warm start the longitudinal part (the engine/brake force, we don't want to preserve anything from the last frame)
  310. w->mLateralPart.WarmStart(*mBody, *w->mContactBody, -w->mContactLateral, inWarmStartImpulseRatio);
  311. }
  312. mPitchRollPart.WarmStart(*mBody, Body::sFixedToWorld, inWarmStartImpulseRatio);
  313. }
  314. bool VehicleConstraint::SolveVelocityConstraint(float inDeltaTime)
  315. {
  316. bool impulse = false;
  317. // Solve suspension
  318. for (Wheel *w : mWheels)
  319. if (w->mContactBody != nullptr)
  320. {
  321. // Suspension spring, note that it can only push and not pull
  322. if (w->mSuspensionPart.IsActive())
  323. impulse |= w->mSuspensionPart.SolveVelocityConstraint(*mBody, *w->mContactBody, w->mWSDirection, 0.0f, FLT_MAX);
  324. // When reaching the minimal suspension length only allow forces pushing the bodies away
  325. if (w->mSuspensionMaxUpPart.IsActive())
  326. impulse |= w->mSuspensionMaxUpPart.SolveVelocityConstraint(*mBody, *w->mContactBody, w->mWSDirection, 0.0f, FLT_MAX);
  327. }
  328. // Solve the horizontal movement of the vehicle
  329. impulse |= mController->SolveLongitudinalAndLateralConstraints(inDeltaTime);
  330. // Apply the pitch / roll constraint to avoid the vehicle from toppling over
  331. if (mPitchRollPart.IsActive())
  332. impulse |= mPitchRollPart.SolveVelocityConstraint(*mBody, Body::sFixedToWorld, mPitchRollRotationAxis, 0, FLT_MAX);
  333. return impulse;
  334. }
  335. bool VehicleConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
  336. {
  337. bool impulse = false;
  338. RMat44 body_transform = mBody->GetWorldTransform();
  339. for (Wheel *w : mWheels)
  340. if (w->mContactBody != nullptr)
  341. {
  342. const WheelSettings *settings = w->mSettings;
  343. // Calculate new contact length as the body may have moved
  344. // TODO: This assumes that only the vehicle moved and not the ground (contact point/normal is stored in world space)
  345. Vec3 ws_direction = body_transform.Multiply3x3(settings->mDirection);
  346. RVec3 ws_position = body_transform * settings->mPosition;
  347. float contact_length = Vec3(w->mContactPosition - ws_position).Dot(ws_direction);
  348. // Check if we reached the 'max up' position
  349. float max_up_error = contact_length - settings->mRadius - settings->mSuspensionMinLength;
  350. if (max_up_error < 0.0f)
  351. {
  352. // Recalculate constraint properties since the body may have moved
  353. Vec3 r1_plus_u, r2;
  354. CalculateWheelContactPoint(body_transform, *w, r1_plus_u, r2);
  355. w->mSuspensionMaxUpPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, ws_direction, 0.0f, max_up_error);
  356. impulse |= w->mSuspensionMaxUpPart.SolvePositionConstraint(*mBody, *w->mContactBody, ws_direction, max_up_error, inBaumgarte);
  357. }
  358. }
  359. // Apply the pitch / roll constraint to avoid the vehicle from toppling over
  360. CalculatePitchRollConstraintProperties(inDeltaTime, body_transform);
  361. if (mPitchRollPart.IsActive())
  362. impulse |= mPitchRollPart.SolvePositionConstraint(*mBody, Body::sFixedToWorld, mCosPitchRollAngle - mCosMaxPitchRollAngle, inBaumgarte);
  363. return impulse;
  364. }
  365. #ifdef JPH_DEBUG_RENDERER
  366. void VehicleConstraint::DrawConstraint(DebugRenderer *inRenderer) const
  367. {
  368. mController->Draw(inRenderer);
  369. }
  370. void VehicleConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const
  371. {
  372. }
  373. #endif // JPH_DEBUG_RENDERER
  374. void VehicleConstraint::SaveState(StateRecorder &inStream) const
  375. {
  376. Constraint::SaveState(inStream);
  377. mController->SaveState(inStream);
  378. for (const Wheel *w : mWheels)
  379. {
  380. inStream.Write(w->mAngularVelocity);
  381. inStream.Write(w->mAngle);
  382. w->mSuspensionPart.SaveState(inStream);
  383. w->mSuspensionMaxUpPart.SaveState(inStream);
  384. w->mLongitudinalPart.SaveState(inStream);
  385. w->mLateralPart.SaveState(inStream);
  386. }
  387. inStream.Write(mPitchRollRotationAxis); // When rotation is too small we use last frame so we need to store it
  388. mPitchRollPart.SaveState(inStream);
  389. }
  390. void VehicleConstraint::RestoreState(StateRecorder &inStream)
  391. {
  392. Constraint::RestoreState(inStream);
  393. mController->RestoreState(inStream);
  394. for (Wheel *w : mWheels)
  395. {
  396. inStream.Read(w->mAngularVelocity);
  397. inStream.Read(w->mAngle);
  398. w->mSuspensionPart.RestoreState(inStream);
  399. w->mSuspensionMaxUpPart.RestoreState(inStream);
  400. w->mLongitudinalPart.RestoreState(inStream);
  401. w->mLateralPart.RestoreState(inStream);
  402. }
  403. inStream.Read(mPitchRollRotationAxis);
  404. mPitchRollPart.RestoreState(inStream);
  405. }
  406. Ref<ConstraintSettings> VehicleConstraint::GetConstraintSettings() const
  407. {
  408. JPH_ASSERT(false); // Not implemented yet
  409. return nullptr;
  410. }
  411. JPH_NAMESPACE_END