VehicleConstraint.cpp 17 KB

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