VehicleConstraint.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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 = true;
  174. for (const Wheel *w : mWheels)
  175. if (abs(w->mAngularVelocity) > DegreesToRadians(10.0f))
  176. allow_sleep = false;
  177. if (mBody->GetAllowSleeping() != allow_sleep)
  178. mBody->SetAllowSleeping(allow_sleep);
  179. }
  180. void VehicleConstraint::BuildIslands(uint32 inConstraintIndex, IslandBuilder &ioBuilder, BodyManager &inBodyManager)
  181. {
  182. // Find dynamic bodies that our wheels are touching
  183. BodyID *body_ids = (BodyID *)alloca((mWheels.size() + 1) * sizeof(BodyID));
  184. int num_bodies = 0;
  185. bool needs_to_activate = false;
  186. for (const Wheel *w : mWheels)
  187. if (w->mContactBody != nullptr)
  188. {
  189. // Avoid adding duplicates
  190. bool duplicate = false;
  191. BodyID id = w->mContactBody->GetID();
  192. for (int i = 0; i < num_bodies; ++i)
  193. if (body_ids[i] == id)
  194. {
  195. duplicate = true;
  196. break;
  197. }
  198. if (duplicate)
  199. continue;
  200. if (w->mContactBody->IsDynamic())
  201. body_ids[num_bodies++] = id;
  202. needs_to_activate |= !w->mContactBody->IsActive();
  203. }
  204. // Activate bodies
  205. if (needs_to_activate)
  206. {
  207. if (!mBody->IsActive())
  208. {
  209. // Our main body is not active, activate it too
  210. body_ids[num_bodies] = mBody->GetID();
  211. inBodyManager.ActivateBodies(body_ids, num_bodies + 1);
  212. }
  213. else
  214. {
  215. // Only activate bodies the wheels are touching
  216. inBodyManager.ActivateBodies(body_ids, num_bodies);
  217. }
  218. }
  219. // Link the bodies into the same island
  220. uint32 min_active_index = Body::cInactiveIndex;
  221. for (int i = 0; i < num_bodies; ++i)
  222. {
  223. const Body &body = inBodyManager.GetBody(body_ids[i]);
  224. min_active_index = min(min_active_index, body.GetIndexInActiveBodiesInternal());
  225. ioBuilder.LinkBodies(mBody->GetIndexInActiveBodiesInternal(), body.GetIndexInActiveBodiesInternal());
  226. }
  227. // Link the constraint in the island
  228. ioBuilder.LinkConstraint(inConstraintIndex, mBody->GetIndexInActiveBodiesInternal(), min_active_index);
  229. }
  230. void VehicleConstraint::CalculateWheelContactPoint(Mat44Arg inBodyTransform, const Wheel &inWheel, Vec3 &outR1PlusU, Vec3 &outR2) const
  231. {
  232. Vec3 contact_pos = inBodyTransform * (inWheel.mSettings->mPosition + inWheel.mSettings->mDirection * inWheel.mContactLength);
  233. outR1PlusU = contact_pos - mBody->GetCenterOfMassPosition();
  234. outR2 = contact_pos - mBody->GetCenterOfMassPosition();
  235. }
  236. void VehicleConstraint::CalculatePitchRollConstraintProperties(float inDeltaTime, Mat44Arg inBodyTransform)
  237. {
  238. // Check if a limit was specified
  239. if (mCosMaxPitchRollAngle < JPH_PI)
  240. {
  241. // Calculate cos of angle between world up vector and vehicle up vector
  242. Vec3 vehicle_up = inBodyTransform.Multiply3x3(mUp);
  243. mCosPitchRollAngle = mUp.Dot(vehicle_up);
  244. if (mCosPitchRollAngle < mCosMaxPitchRollAngle)
  245. {
  246. // Calculate rotation axis to rotate vehicle towards up
  247. Vec3 rotation_axis = mUp.Cross(vehicle_up);
  248. float len = rotation_axis.Length();
  249. if (len > 0.0f)
  250. mPitchRollRotationAxis = rotation_axis / len;
  251. mPitchRollPart.CalculateConstraintProperties(inDeltaTime, *mBody, Body::sFixedToWorld, mPitchRollRotationAxis);
  252. }
  253. else
  254. mPitchRollPart.Deactivate();
  255. }
  256. else
  257. mPitchRollPart.Deactivate();
  258. }
  259. void VehicleConstraint::SetupVelocityConstraint(float inDeltaTime)
  260. {
  261. Mat44 body_transform = mBody->GetWorldTransform();
  262. for (Wheel *w : mWheels)
  263. if (w->mContactBody != nullptr)
  264. {
  265. const WheelSettings *settings = w->mSettings;
  266. Vec3 r1_plus_u, r2;
  267. CalculateWheelContactPoint(body_transform, *w, r1_plus_u, r2);
  268. // Suspension spring
  269. if (settings->mSuspensionMaxLength > settings->mSuspensionMinLength)
  270. 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);
  271. else
  272. w->mSuspensionPart.Deactivate();
  273. // Check if we reached the 'max up' position
  274. float max_up_error = w->mContactLength - settings->mRadius - settings->mSuspensionMinLength;
  275. if (max_up_error < 0.0f)
  276. w->mSuspensionMaxUpPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, w->mWSDirection, 0.0f, max_up_error);
  277. else
  278. w->mSuspensionMaxUpPart.Deactivate();
  279. // Friction and propulsion
  280. w->mLongitudinalPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, -w->mContactLongitudinal, 0.0f, 0.0f, 0.0f, 0.0f);
  281. w->mLateralPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, -w->mContactLateral, 0.0f, 0.0f, 0.0f, 0.0f);
  282. }
  283. else
  284. {
  285. // No contact -> disable everything
  286. w->mSuspensionPart.Deactivate();
  287. w->mSuspensionMaxUpPart.Deactivate();
  288. w->mLongitudinalPart.Deactivate();
  289. w->mLateralPart.Deactivate();
  290. }
  291. CalculatePitchRollConstraintProperties(inDeltaTime, body_transform);
  292. }
  293. void VehicleConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio)
  294. {
  295. for (Wheel *w : mWheels)
  296. if (w->mContactBody != nullptr)
  297. {
  298. w->mSuspensionPart.WarmStart(*mBody, *w->mContactBody, w->mWSDirection, inWarmStartImpulseRatio);
  299. w->mSuspensionMaxUpPart.WarmStart(*mBody, *w->mContactBody, w->mWSDirection, inWarmStartImpulseRatio);
  300. 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)
  301. w->mLateralPart.WarmStart(*mBody, *w->mContactBody, -w->mContactLateral, inWarmStartImpulseRatio);
  302. }
  303. mPitchRollPart.WarmStart(*mBody, Body::sFixedToWorld, inWarmStartImpulseRatio);
  304. }
  305. bool VehicleConstraint::SolveVelocityConstraint(float inDeltaTime)
  306. {
  307. bool impulse = false;
  308. // Solve suspension
  309. for (Wheel *w : mWheels)
  310. if (w->mContactBody != nullptr)
  311. {
  312. // Suspension spring, note that it can only push and not pull
  313. if (w->mSuspensionPart.IsActive())
  314. impulse |= w->mSuspensionPart.SolveVelocityConstraint(*mBody, *w->mContactBody, w->mWSDirection, 0.0f, FLT_MAX);
  315. // When reaching the minimal suspension length only allow forces pushing the bodies away
  316. if (w->mSuspensionMaxUpPart.IsActive())
  317. impulse |= w->mSuspensionMaxUpPart.SolveVelocityConstraint(*mBody, *w->mContactBody, w->mWSDirection, 0.0f, FLT_MAX);
  318. }
  319. // Solve the horizontal movement of the vehicle
  320. impulse |= mController->SolveLongitudinalAndLateralConstraints(inDeltaTime);
  321. // Apply the pitch / roll constraint to avoid the vehicle from toppling over
  322. impulse |= mPitchRollPart.SolveVelocityConstraint(*mBody, Body::sFixedToWorld, mPitchRollRotationAxis, 0, FLT_MAX);
  323. return impulse;
  324. }
  325. bool VehicleConstraint::SolvePositionConstraint(float inDeltaTime, float inBaumgarte)
  326. {
  327. bool impulse = false;
  328. Mat44 body_transform = mBody->GetWorldTransform();
  329. for (Wheel *w : mWheels)
  330. if (w->mContactBody != nullptr)
  331. {
  332. const WheelSettings *settings = w->mSettings;
  333. // Calculate new contact length as the body may have moved
  334. // TODO: This assumes that only the vehicle moved and not the ground (contact point/normal is stored in world space)
  335. Vec3 ws_direction = body_transform.Multiply3x3(settings->mDirection);
  336. Vec3 ws_position = body_transform * settings->mPosition;
  337. float contact_length = (w->mContactPosition - ws_position).Dot(ws_direction);
  338. // Check if we reached the 'max up' position
  339. float max_up_error = contact_length - settings->mRadius - settings->mSuspensionMinLength;
  340. if (max_up_error < 0.0f)
  341. {
  342. // Recalculate constraint properties since the body may have moved
  343. Vec3 r1_plus_u, r2;
  344. CalculateWheelContactPoint(body_transform, *w, r1_plus_u, r2);
  345. w->mSuspensionMaxUpPart.CalculateConstraintProperties(inDeltaTime, *mBody, r1_plus_u, *w->mContactBody, r2, ws_direction, 0.0f, max_up_error);
  346. impulse |= w->mSuspensionMaxUpPart.SolvePositionConstraint(*mBody, *w->mContactBody, ws_direction, max_up_error, inBaumgarte);
  347. }
  348. }
  349. // Apply the pitch / roll constraint to avoid the vehicle from toppling over
  350. CalculatePitchRollConstraintProperties(inDeltaTime, body_transform);
  351. if (mPitchRollPart.IsActive())
  352. impulse |= mPitchRollPart.SolvePositionConstraint(*mBody, Body::sFixedToWorld, mCosPitchRollAngle - mCosMaxPitchRollAngle, inBaumgarte);
  353. return impulse;
  354. }
  355. #ifdef JPH_DEBUG_RENDERER
  356. void VehicleConstraint::DrawConstraint(DebugRenderer *inRenderer) const
  357. {
  358. mController->Draw(inRenderer);
  359. }
  360. void VehicleConstraint::DrawConstraintLimits(DebugRenderer *inRenderer) const
  361. {
  362. }
  363. #endif // JPH_DEBUG_RENDERER
  364. void VehicleConstraint::SaveState(StateRecorder &inStream) const
  365. {
  366. Constraint::SaveState(inStream);
  367. mController->SaveState(inStream);
  368. for (const Wheel *w : mWheels)
  369. {
  370. inStream.Write(w->mAngularVelocity);
  371. inStream.Write(w->mAngle);
  372. w->mSuspensionPart.SaveState(inStream);
  373. w->mSuspensionMaxUpPart.SaveState(inStream);
  374. w->mLongitudinalPart.SaveState(inStream);
  375. w->mLateralPart.SaveState(inStream);
  376. }
  377. inStream.Write(mPitchRollRotationAxis); // When rotation is too small we use last frame so we need to store it
  378. mPitchRollPart.SaveState(inStream);
  379. }
  380. void VehicleConstraint::RestoreState(StateRecorder &inStream)
  381. {
  382. Constraint::RestoreState(inStream);
  383. mController->RestoreState(inStream);
  384. for (Wheel *w : mWheels)
  385. {
  386. inStream.Read(w->mAngularVelocity);
  387. inStream.Read(w->mAngle);
  388. w->mSuspensionPart.RestoreState(inStream);
  389. w->mSuspensionMaxUpPart.RestoreState(inStream);
  390. w->mLongitudinalPart.RestoreState(inStream);
  391. w->mLateralPart.RestoreState(inStream);
  392. }
  393. inStream.Read(mPitchRollRotationAxis);
  394. mPitchRollPart.RestoreState(inStream);
  395. }
  396. JPH_NAMESPACE_END