TrackedVehicleController.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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/TrackedVehicleController.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. #ifdef JPH_DEBUG_RENDERER
  11. #include <Jolt/Renderer/DebugRenderer.h>
  12. #endif // JPH_DEBUG_RENDERER
  13. JPH_NAMESPACE_BEGIN
  14. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(TrackedVehicleControllerSettings)
  15. {
  16. JPH_ADD_BASE_CLASS(TrackedVehicleControllerSettings, VehicleControllerSettings)
  17. JPH_ADD_ATTRIBUTE(TrackedVehicleControllerSettings, mEngine)
  18. JPH_ADD_ATTRIBUTE(TrackedVehicleControllerSettings, mTransmission)
  19. JPH_ADD_ATTRIBUTE(TrackedVehicleControllerSettings, mTracks)
  20. }
  21. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(WheelSettingsTV)
  22. {
  23. JPH_ADD_ATTRIBUTE(WheelSettingsTV, mLongitudinalFriction)
  24. JPH_ADD_ATTRIBUTE(WheelSettingsTV, mLateralFriction)
  25. }
  26. void WheelSettingsTV::SaveBinaryState(StreamOut &inStream) const
  27. {
  28. inStream.Write(mLongitudinalFriction);
  29. inStream.Write(mLateralFriction);
  30. }
  31. void WheelSettingsTV::RestoreBinaryState(StreamIn &inStream)
  32. {
  33. inStream.Read(mLongitudinalFriction);
  34. inStream.Read(mLateralFriction);
  35. }
  36. WheelTV::WheelTV(const WheelSettingsTV &inSettings) :
  37. Wheel(inSettings)
  38. {
  39. }
  40. void WheelTV::CalculateAngularVelocity(const VehicleConstraint &inConstraint)
  41. {
  42. const WheelSettingsTV *settings = GetSettings();
  43. const Wheels &wheels = inConstraint.GetWheels();
  44. const VehicleTrack &track = static_cast<const TrackedVehicleController *>(inConstraint.GetController())->GetTracks()[mTrackIndex];
  45. // Calculate angular velocity of this wheel
  46. mAngularVelocity = track.mAngularVelocity * wheels[track.mDrivenWheel]->GetSettings()->mRadius / settings->mRadius;
  47. }
  48. void WheelTV::Update(float inDeltaTime, const VehicleConstraint &inConstraint)
  49. {
  50. CalculateAngularVelocity(inConstraint);
  51. // Update rotation of wheel
  52. mAngle = fmod(mAngle + mAngularVelocity * inDeltaTime, 2.0f * JPH_PI);
  53. // Reset brake impulse, will be set during post collision again
  54. mBrakeImpulse = 0.0f;
  55. if (mContactBody != nullptr)
  56. {
  57. // Friction at the point of this wheel between track and floor
  58. const WheelSettingsTV *settings = GetSettings();
  59. VehicleConstraint::CombineFunction combine_friction = inConstraint.GetCombineFriction();
  60. mCombinedLongitudinalFriction = combine_friction(settings->mLongitudinalFriction, *mContactBody, mContactSubShapeID);
  61. mCombinedLateralFriction = combine_friction(settings->mLateralFriction, *mContactBody, mContactSubShapeID);
  62. }
  63. else
  64. {
  65. // No collision
  66. mCombinedLongitudinalFriction = mCombinedLateralFriction = 0.0f;
  67. }
  68. }
  69. VehicleController *TrackedVehicleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const
  70. {
  71. return new TrackedVehicleController(*this, inConstraint);
  72. }
  73. TrackedVehicleControllerSettings::TrackedVehicleControllerSettings()
  74. {
  75. // Numbers guestimated from: https://en.wikipedia.org/wiki/M1_Abrams
  76. mEngine.mMinRPM = 500.0f;
  77. mEngine.mMaxRPM = 4000.0f;
  78. mEngine.mMaxTorque = 500.0f; // Note actual torque for M1 is around 5000 but we need a reduced mass in order to keep the simulation sane
  79. mTransmission.mShiftDownRPM = 1000.0f;
  80. mTransmission.mShiftUpRPM = 3500.0f;
  81. mTransmission.mGearRatios = { 4.0f, 3.0f, 2.0f, 1.0f };
  82. mTransmission.mReverseGearRatios = { -4.0f, -3.0f };
  83. }
  84. void TrackedVehicleControllerSettings::SaveBinaryState(StreamOut &inStream) const
  85. {
  86. mEngine.SaveBinaryState(inStream);
  87. mTransmission.SaveBinaryState(inStream);
  88. for (const VehicleTrackSettings &t : mTracks)
  89. t.SaveBinaryState(inStream);
  90. }
  91. void TrackedVehicleControllerSettings::RestoreBinaryState(StreamIn &inStream)
  92. {
  93. mEngine.RestoreBinaryState(inStream);
  94. mTransmission.RestoreBinaryState(inStream);
  95. for (VehicleTrackSettings &t : mTracks)
  96. t.RestoreBinaryState(inStream);
  97. }
  98. TrackedVehicleController::TrackedVehicleController(const TrackedVehicleControllerSettings &inSettings, VehicleConstraint &inConstraint) :
  99. VehicleController(inConstraint)
  100. {
  101. // Copy engine settings
  102. static_cast<VehicleEngineSettings &>(mEngine) = inSettings.mEngine;
  103. JPH_ASSERT(inSettings.mEngine.mMinRPM >= 0.0f);
  104. JPH_ASSERT(inSettings.mEngine.mMinRPM <= inSettings.mEngine.mMaxRPM);
  105. // Copy transmission settings
  106. static_cast<VehicleTransmissionSettings &>(mTransmission) = inSettings.mTransmission;
  107. #ifdef JPH_ENABLE_ASSERTS
  108. for (float r : inSettings.mTransmission.mGearRatios)
  109. JPH_ASSERT(r > 0.0f);
  110. for (float r : inSettings.mTransmission.mReverseGearRatios)
  111. JPH_ASSERT(r < 0.0f);
  112. #endif // JPH_ENABLE_ASSERTS
  113. JPH_ASSERT(inSettings.mTransmission.mSwitchTime >= 0.0f);
  114. JPH_ASSERT(inSettings.mTransmission.mShiftDownRPM > 0.0f);
  115. JPH_ASSERT(inSettings.mTransmission.mMode != ETransmissionMode::Auto || inSettings.mTransmission.mShiftUpRPM < inSettings.mEngine.mMaxRPM);
  116. JPH_ASSERT(inSettings.mTransmission.mShiftUpRPM > inSettings.mTransmission.mShiftDownRPM);
  117. // Copy track settings
  118. for (uint i = 0; i < size(mTracks); ++i)
  119. {
  120. const VehicleTrackSettings &d = inSettings.mTracks[i];
  121. static_cast<VehicleTrackSettings &>(mTracks[i]) = d;
  122. JPH_ASSERT(d.mInertia >= 0.0f);
  123. JPH_ASSERT(d.mAngularDamping >= 0.0f);
  124. JPH_ASSERT(d.mMaxBrakeTorque >= 0.0f);
  125. JPH_ASSERT(d.mDifferentialRatio > 0.0f);
  126. }
  127. }
  128. bool TrackedVehicleController::AllowSleep() const
  129. {
  130. return mForwardInput == 0.0f // No user input
  131. && mTransmission.AllowSleep() // Transmission is not shifting
  132. && mEngine.AllowSleep(); // Engine is idling
  133. }
  134. void TrackedVehicleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
  135. {
  136. Wheels &wheels = mConstraint.GetWheels();
  137. // Fill in track index
  138. for (size_t t = 0; t < size(mTracks); ++t)
  139. for (uint w : mTracks[t].mWheels)
  140. static_cast<WheelTV *>(wheels[w])->mTrackIndex = (uint)t;
  141. // Angular damping: dw/dt = -c * w
  142. // Solution: w(t) = w(0) * e^(-c * t) or w2 = w1 * e^(-c * dt)
  143. // Taylor expansion of e^(-c * dt) = 1 - c * dt + ...
  144. // Since dt is usually in the order of 1/60 and c is a low number too this approximation is good enough
  145. for (VehicleTrack &t : mTracks)
  146. t.mAngularVelocity *= max(0.0f, 1.0f - t.mAngularDamping * inDeltaTime);
  147. }
  148. void TrackedVehicleController::SyncLeftRightTracks()
  149. {
  150. // Apply left to right ratio according to track inertias
  151. VehicleTrack &tl = mTracks[(int)ETrackSide::Left];
  152. VehicleTrack &tr = mTracks[(int)ETrackSide::Right];
  153. if (mLeftRatio * mRightRatio > 0.0f)
  154. {
  155. // Solve: (tl.mAngularVelocity + dl) / (tr.mAngularVelocity + dr) = mLeftRatio / mRightRatio and dl * tr.mInertia = -dr * tl.mInertia, where dl/dr are the delta angular velocities for left and right tracks
  156. float impulse = (mLeftRatio * tr.mAngularVelocity - mRightRatio * tl.mAngularVelocity) / (mLeftRatio * tr.mInertia + mRightRatio * tl.mInertia);
  157. tl.mAngularVelocity += impulse * tl.mInertia;
  158. tr.mAngularVelocity -= impulse * tr.mInertia;
  159. }
  160. else
  161. {
  162. // Solve: (tl.mAngularVelocity + dl) / (tr.mAngularVelocity + dr) = mLeftRatio / mRightRatio and dl * tr.mInertia = dr * tl.mInertia, where dl/dr are the delta angular velocities for left and right tracks
  163. float impulse = (mLeftRatio * tr.mAngularVelocity - mRightRatio * tl.mAngularVelocity) / (mRightRatio * tl.mInertia - mLeftRatio * tr.mInertia);
  164. tl.mAngularVelocity += impulse * tl.mInertia;
  165. tr.mAngularVelocity += impulse * tr.mInertia;
  166. }
  167. }
  168. void TrackedVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
  169. {
  170. JPH_PROFILE_FUNCTION();
  171. Wheels &wheels = mConstraint.GetWheels();
  172. // Update wheel angle, do this before applying torque to the wheels (as friction will slow them down again)
  173. for (Wheel *w_base : wheels)
  174. {
  175. WheelTV *w = static_cast<WheelTV *>(w_base);
  176. w->Update(inDeltaTime, mConstraint);
  177. }
  178. // First calculate engine speed based on speed of all wheels
  179. bool can_engine_apply_torque = false;
  180. if (mTransmission.GetCurrentGear() != 0 && mTransmission.GetClutchFriction() > 1.0e-3f)
  181. {
  182. float transmission_ratio = mTransmission.GetCurrentRatio();
  183. bool forward = transmission_ratio >= 0.0f;
  184. float fastest_wheel_speed = forward? -FLT_MAX : FLT_MAX;
  185. for (const VehicleTrack &t : mTracks)
  186. {
  187. if (forward)
  188. fastest_wheel_speed = max(fastest_wheel_speed, t.mAngularVelocity * t.mDifferentialRatio);
  189. else
  190. fastest_wheel_speed = min(fastest_wheel_speed, t.mAngularVelocity * t.mDifferentialRatio);
  191. for (uint w : t.mWheels)
  192. if (wheels[w]->HasContact())
  193. {
  194. can_engine_apply_torque = true;
  195. break;
  196. }
  197. }
  198. // Update RPM only if the tracks are connected to the engine
  199. if (fastest_wheel_speed > -FLT_MAX && fastest_wheel_speed < FLT_MAX)
  200. mEngine.SetCurrentRPM(fastest_wheel_speed * mTransmission.GetCurrentRatio() * VehicleEngine::cAngularVelocityToRPM);
  201. }
  202. else
  203. {
  204. // Update engine with damping
  205. mEngine.ApplyDamping(inDeltaTime);
  206. // In auto transmission mode, don't accelerate the engine when switching gears
  207. float forward_input = mTransmission.mMode == ETransmissionMode::Manual? abs(mForwardInput) : 0.0f;
  208. // Engine not connected to wheels, update RPM based on engine inertia alone
  209. mEngine.ApplyTorque(mEngine.GetTorque(forward_input), inDeltaTime);
  210. }
  211. // Update transmission
  212. // Note: only allow switching gears up when the tracks are rolling in the same direction
  213. mTransmission.Update(inDeltaTime, mEngine.GetCurrentRPM(), mForwardInput, mLeftRatio * mRightRatio > 0.0f && can_engine_apply_torque);
  214. // Calculate the amount of torque the transmission gives to the differentials
  215. float transmission_ratio = mTransmission.GetCurrentRatio();
  216. float transmission_torque = mTransmission.GetClutchFriction() * transmission_ratio * mEngine.GetTorque(abs(mForwardInput));
  217. if (transmission_torque != 0.0f)
  218. {
  219. // Apply the transmission torque to the wheels
  220. for (uint i = 0; i < size(mTracks); ++i)
  221. {
  222. VehicleTrack &t = mTracks[i];
  223. // Get wheel rotation ratio for this track
  224. float ratio = i == 0? mLeftRatio : mRightRatio;
  225. // Calculate the max angular velocity of the driven wheel of the track given current engine RPM
  226. // Note this adds 0.1% slop to avoid numerical accuracy issues
  227. float track_max_angular_velocity = mEngine.GetCurrentRPM() / (transmission_ratio * t.mDifferentialRatio * ratio * VehicleEngine::cAngularVelocityToRPM) * 1.001f;
  228. // Calculate torque on the driven wheel
  229. float differential_torque = t.mDifferentialRatio * ratio * transmission_torque;
  230. // Apply torque to driven wheel
  231. if (t.mAngularVelocity * track_max_angular_velocity < 0.0f || abs(t.mAngularVelocity) < abs(track_max_angular_velocity))
  232. t.mAngularVelocity += differential_torque * inDeltaTime / t.mInertia;
  233. }
  234. }
  235. // Ensure that we have the correct ratio between the two tracks
  236. SyncLeftRightTracks();
  237. // Braking
  238. for (VehicleTrack &t : mTracks)
  239. {
  240. // Calculate brake torque
  241. float brake_torque = mBrakeInput * t.mMaxBrakeTorque;
  242. if (brake_torque > 0.0f)
  243. {
  244. // Calculate how much torque is needed to stop the track from rotating in this time step
  245. float brake_torque_to_lock_track = abs(t.mAngularVelocity) * t.mInertia / inDeltaTime;
  246. if (brake_torque > brake_torque_to_lock_track)
  247. {
  248. // Wheels are locked
  249. t.mAngularVelocity = 0.0f;
  250. brake_torque -= brake_torque_to_lock_track;
  251. }
  252. else
  253. {
  254. // Slow down the track
  255. t.mAngularVelocity -= Sign(t.mAngularVelocity) * brake_torque * inDeltaTime / t.mInertia;
  256. }
  257. }
  258. if (brake_torque > 0.0f)
  259. {
  260. // Sum the radius of all wheels touching the floor
  261. float total_radius = 0.0f;
  262. for (uint wheel_index : t.mWheels)
  263. {
  264. const WheelTV *w = static_cast<WheelTV *>(wheels[wheel_index]);
  265. if (w->HasContact())
  266. total_radius += w->GetSettings()->mRadius;
  267. }
  268. if (total_radius > 0.0f)
  269. {
  270. brake_torque /= total_radius;
  271. for (uint wheel_index : t.mWheels)
  272. {
  273. WheelTV *w = static_cast<WheelTV *>(wheels[wheel_index]);
  274. if (w->HasContact())
  275. {
  276. // Impulse: p = F * dt = Torque / Wheel_Radius * dt, Torque = Total_Torque * Wheel_Radius / Summed_Radius => p = Total_Torque * dt / Summed_Radius
  277. w->mBrakeImpulse = brake_torque * inDeltaTime;
  278. }
  279. }
  280. }
  281. }
  282. }
  283. // Update wheel angular velocity based on that of the track
  284. for (Wheel *w_base : wheels)
  285. {
  286. WheelTV *w = static_cast<WheelTV *>(w_base);
  287. w->CalculateAngularVelocity(mConstraint);
  288. }
  289. }
  290. bool TrackedVehicleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)
  291. {
  292. bool impulse = false;
  293. for (Wheel *w_base : mConstraint.GetWheels())
  294. if (w_base->HasContact())
  295. {
  296. WheelTV *w = static_cast<WheelTV *>(w_base);
  297. const WheelSettingsTV *settings = w->GetSettings();
  298. VehicleTrack &track = mTracks[w->mTrackIndex];
  299. // Calculate max impulse that we can apply on the ground
  300. float max_longitudinal_friction_impulse = w->mCombinedLongitudinalFriction * w->GetSuspensionLambda();
  301. // Calculate relative velocity between wheel contact point and floor in longitudinal direction
  302. Vec3 relative_velocity = mConstraint.GetVehicleBody()->GetPointVelocity(w->GetContactPosition()) - w->GetContactPointVelocity();
  303. float relative_longitudinal_velocity = relative_velocity.Dot(w->GetContactLongitudinal());
  304. // Calculate brake force to apply
  305. float min_longitudinal_impulse, max_longitudinal_impulse;
  306. if (w->mBrakeImpulse != 0.0f)
  307. {
  308. // Limit brake force by max tire friction
  309. float brake_impulse = min(w->mBrakeImpulse, max_longitudinal_friction_impulse);
  310. // Check which direction the brakes should be applied (we don't want to apply an impulse that would accelerate the vehicle)
  311. if (relative_longitudinal_velocity >= 0.0f)
  312. {
  313. min_longitudinal_impulse = -brake_impulse;
  314. max_longitudinal_impulse = 0.0f;
  315. }
  316. else
  317. {
  318. min_longitudinal_impulse = 0.0f;
  319. max_longitudinal_impulse = brake_impulse;
  320. }
  321. // Longitudinal impulse, note that we assume that once the wheels are locked that the brakes have more than enough torque to keep the wheels locked so we exclude any rotation deltas
  322. impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);
  323. }
  324. else
  325. {
  326. // Assume we want to apply an angular impulse that makes the delta velocity between track and ground zero in one time step, calculate the amount of linear impulse needed to do that
  327. float desired_angular_velocity = relative_longitudinal_velocity / settings->mRadius;
  328. float linear_impulse = (track.mAngularVelocity - desired_angular_velocity) * track.mInertia / settings->mRadius;
  329. // Limit the impulse by max track friction
  330. min_longitudinal_impulse = max_longitudinal_impulse = w->GetLongitudinalLambda() + Sign(linear_impulse) * min(abs(linear_impulse), max_longitudinal_friction_impulse);
  331. // Longitudinal impulse
  332. float prev_lambda = w->GetLongitudinalLambda();
  333. impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);
  334. // Update the angular velocity of the track according to the lambda that was applied
  335. track.mAngularVelocity -= (w->GetLongitudinalLambda() - prev_lambda) * settings->mRadius / track.mInertia;
  336. SyncLeftRightTracks();
  337. }
  338. }
  339. for (Wheel *w_base : mConstraint.GetWheels())
  340. if (w_base->HasContact())
  341. {
  342. WheelTV *w = static_cast<WheelTV *>(w_base);
  343. // Update angular velocity of wheel for the next iteration
  344. w->CalculateAngularVelocity(mConstraint);
  345. // Lateral friction
  346. float max_lateral_friction_impulse = w->mCombinedLateralFriction * w->GetSuspensionLambda();
  347. impulse |= w->SolveLateralConstraintPart(mConstraint, -max_lateral_friction_impulse, max_lateral_friction_impulse);
  348. }
  349. return impulse;
  350. }
  351. #ifdef JPH_DEBUG_RENDERER
  352. void TrackedVehicleController::Draw(DebugRenderer *inRenderer) const
  353. {
  354. float constraint_size = mConstraint.GetDrawConstraintSize();
  355. // Draw RPM
  356. Body *body = mConstraint.GetVehicleBody();
  357. Vec3 rpm_meter_up = body->GetRotation() * mConstraint.GetLocalUp();
  358. RVec3 rpm_meter_pos = body->GetPosition() + body->GetRotation() * mRPMMeterPosition;
  359. Vec3 rpm_meter_fwd = body->GetRotation() * mConstraint.GetLocalForward();
  360. mEngine.DrawRPM(inRenderer, rpm_meter_pos, rpm_meter_fwd, rpm_meter_up, mRPMMeterSize, mTransmission.mShiftDownRPM, mTransmission.mShiftUpRPM);
  361. // Draw current vehicle state
  362. String status = StringFormat("Forward: %.1f, LRatio: %.1f, RRatio: %.1f, Brake: %.1f\n"
  363. "Gear: %d, Clutch: %.1f, EngineRPM: %.0f, V: %.1f km/h",
  364. (double)mForwardInput, (double)mLeftRatio, (double)mRightRatio, (double)mBrakeInput,
  365. mTransmission.GetCurrentGear(), (double)mTransmission.GetClutchFriction(), (double)mEngine.GetCurrentRPM(), (double)body->GetLinearVelocity().Length() * 3.6);
  366. inRenderer->DrawText3D(body->GetPosition(), status, Color::sWhite, constraint_size);
  367. for (const VehicleTrack &t : mTracks)
  368. {
  369. const WheelTV *w = static_cast<const WheelTV *>(mConstraint.GetWheels()[t.mDrivenWheel]);
  370. const WheelSettings *settings = w->GetSettings();
  371. // Calculate where the suspension attaches to the body in world space
  372. RVec3 ws_position = body->GetCenterOfMassPosition() + body->GetRotation() * (settings->mPosition - body->GetShape()->GetCenterOfMass());
  373. DebugRenderer::sInstance->DrawText3D(ws_position, StringFormat("W: %.1f", (double)t.mAngularVelocity), Color::sWhite, constraint_size);
  374. }
  375. RMat44 body_transform = body->GetWorldTransform();
  376. for (const Wheel *w_base : mConstraint.GetWheels())
  377. {
  378. const WheelTV *w = static_cast<const WheelTV *>(w_base);
  379. const WheelSettings *settings = w->GetSettings();
  380. // Calculate where the suspension attaches to the body in world space
  381. RVec3 ws_position = body_transform * settings->mPosition;
  382. Vec3 ws_direction = body_transform.Multiply3x3(settings->mSuspensionDirection);
  383. // Draw suspension
  384. RVec3 min_suspension_pos = ws_position + ws_direction * settings->mSuspensionMinLength;
  385. RVec3 max_suspension_pos = ws_position + ws_direction * settings->mSuspensionMaxLength;
  386. inRenderer->DrawLine(ws_position, min_suspension_pos, Color::sRed);
  387. inRenderer->DrawLine(min_suspension_pos, max_suspension_pos, Color::sGreen);
  388. // Draw current length
  389. RVec3 wheel_pos = ws_position + ws_direction * w->GetSuspensionLength();
  390. inRenderer->DrawMarker(wheel_pos, w->GetSuspensionLength() < settings->mSuspensionMinLength? Color::sRed : Color::sGreen, constraint_size);
  391. // Draw wheel basis
  392. Vec3 wheel_forward, wheel_up, wheel_right;
  393. mConstraint.GetWheelLocalBasis(w, wheel_forward, wheel_up, wheel_right);
  394. wheel_forward = body_transform.Multiply3x3(wheel_forward);
  395. wheel_up = body_transform.Multiply3x3(wheel_up);
  396. wheel_right = body_transform.Multiply3x3(wheel_right);
  397. Vec3 steering_axis = body_transform.Multiply3x3(settings->mSteeringAxis);
  398. inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_forward, Color::sRed);
  399. inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_up, Color::sGreen);
  400. inRenderer->DrawLine(wheel_pos, wheel_pos + wheel_right, Color::sBlue);
  401. inRenderer->DrawLine(wheel_pos, wheel_pos + steering_axis, Color::sYellow);
  402. if (w->HasContact())
  403. {
  404. // Draw contact
  405. inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactNormal(), Color::sYellow);
  406. inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLongitudinal(), Color::sRed);
  407. inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLateral(), Color::sBlue);
  408. DebugRenderer::sInstance->DrawText3D(w->GetContactPosition(), StringFormat("S: %.2f", (double)w->GetSuspensionLength()), Color::sWhite, constraint_size);
  409. }
  410. }
  411. }
  412. #endif // JPH_DEBUG_RENDERER
  413. void TrackedVehicleController::SaveState(StateRecorder &inStream) const
  414. {
  415. inStream.Write(mForwardInput);
  416. inStream.Write(mLeftRatio);
  417. inStream.Write(mRightRatio);
  418. inStream.Write(mBrakeInput);
  419. mEngine.SaveState(inStream);
  420. mTransmission.SaveState(inStream);
  421. for (const VehicleTrack &t : mTracks)
  422. t.SaveState(inStream);
  423. }
  424. void TrackedVehicleController::RestoreState(StateRecorder &inStream)
  425. {
  426. inStream.Read(mForwardInput);
  427. inStream.Read(mLeftRatio);
  428. inStream.Read(mRightRatio);
  429. inStream.Read(mBrakeInput);
  430. mEngine.RestoreState(inStream);
  431. mTransmission.RestoreState(inStream);
  432. for (VehicleTrack &t : mTracks)
  433. t.RestoreState(inStream);
  434. }
  435. JPH_NAMESPACE_END