WheeledVehicleController.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <Jolt.h>
  4. #include <Physics/Vehicle/WheeledVehicleController.h>
  5. #include <Physics/PhysicsSystem.h>
  6. #include <ObjectStream/TypeDeclarations.h>
  7. #include <Core/StreamIn.h>
  8. #include <Core/StreamOut.h>
  9. namespace JPH {
  10. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(WheeledVehicleControllerSettings)
  11. {
  12. JPH_ADD_BASE_CLASS(WheeledVehicleControllerSettings, VehicleControllerSettings)
  13. JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mEngine)
  14. JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mTransmission)
  15. JPH_ADD_ATTRIBUTE(WheeledVehicleControllerSettings, mDifferentials)
  16. }
  17. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(WheelSettingsWV)
  18. {
  19. JPH_ADD_ATTRIBUTE(WheelSettingsWV, mInertia)
  20. JPH_ADD_ATTRIBUTE(WheelSettingsWV, mAngularDamping)
  21. JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxSteerAngle)
  22. JPH_ADD_ATTRIBUTE(WheelSettingsWV, mLongitudinalFriction)
  23. JPH_ADD_ATTRIBUTE(WheelSettingsWV, mLateralFriction)
  24. JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxBrakeTorque)
  25. JPH_ADD_ATTRIBUTE(WheelSettingsWV, mMaxHandBrakeTorque)
  26. }
  27. WheelSettingsWV::WheelSettingsWV()
  28. {
  29. mLongitudinalFriction.Reserve(3);
  30. mLongitudinalFriction.AddPoint(0.0f, 0.0f);
  31. mLongitudinalFriction.AddPoint(0.06f, 1.2f);
  32. mLongitudinalFriction.AddPoint(0.2f, 1.0f);
  33. mLateralFriction.Reserve(3);
  34. mLateralFriction.AddPoint(0.0f, 0.0f);
  35. mLateralFriction.AddPoint(3.0f, 1.2f);
  36. mLateralFriction.AddPoint(20.0f, 1.0f);
  37. }
  38. void WheelSettingsWV::SaveBinaryState(StreamOut &inStream) const
  39. {
  40. inStream.Write(mInertia);
  41. inStream.Write(mAngularDamping);
  42. inStream.Write(mMaxSteerAngle);
  43. mLongitudinalFriction.SaveBinaryState(inStream);
  44. mLateralFriction.SaveBinaryState(inStream);
  45. inStream.Write(mMaxBrakeTorque);
  46. inStream.Write(mMaxHandBrakeTorque);
  47. }
  48. void WheelSettingsWV::RestoreBinaryState(StreamIn &inStream)
  49. {
  50. inStream.Read(mInertia);
  51. inStream.Read(mAngularDamping);
  52. inStream.Read(mMaxSteerAngle);
  53. mLongitudinalFriction.RestoreBinaryState(inStream);
  54. mLateralFriction.RestoreBinaryState(inStream);
  55. inStream.Read(mMaxBrakeTorque);
  56. inStream.Read(mMaxHandBrakeTorque);
  57. }
  58. WheelWV::WheelWV(const WheelSettingsWV &inSettings) :
  59. Wheel(inSettings)
  60. {
  61. JPH_ASSERT(inSettings.mInertia >= 0.0f);
  62. JPH_ASSERT(inSettings.mAngularDamping >= 0.0f);
  63. JPH_ASSERT(abs(inSettings.mMaxSteerAngle) <= 0.5f * JPH_PI);
  64. JPH_ASSERT(inSettings.mMaxBrakeTorque >= 0.0f);
  65. JPH_ASSERT(inSettings.mMaxHandBrakeTorque >= 0.0f);
  66. }
  67. void WheelWV::Update(float inDeltaTime, const VehicleConstraint &inConstraint)
  68. {
  69. const WheelSettingsWV *settings = GetSettings();
  70. // Angular damping: dw/dt = -c * w
  71. // Solution: w(t) = w(0) * e^(-c * t) or w2 = w1 * e^(-c * dt)
  72. // Taylor expansion of e^(-c * dt) = 1 - c * dt + ...
  73. // Since dt is usually in the order of 1/60 and c is a low number too this approximation is good enough
  74. mAngularVelocity *= max(0.0f, 1.0f - settings->mAngularDamping * inDeltaTime);
  75. // Update rotation of wheel
  76. mAngle = fmod(mAngle + mAngularVelocity * inDeltaTime, 2.0f * JPH_PI);
  77. if (mContactBody != nullptr)
  78. {
  79. Body *body = inConstraint.GetVehicleBody();
  80. // Calculate relative velocity between wheel contact point and floor
  81. Vec3 relative_velocity = body->GetPointVelocity(mContactPosition) - mContactPointVelocity;
  82. // Cancel relative velocity in the normal plane
  83. relative_velocity -= mContactNormal.Dot(relative_velocity) * mContactNormal;
  84. float relative_longitudinal_velocity = relative_velocity.Dot(mContactLongitudinal);
  85. // Calculate longitudinal friction based on difference between velocity of rolling wheel and drive surface
  86. float longitudinal_slip = relative_longitudinal_velocity != 0.0f? abs((mAngularVelocity * settings->mRadius - relative_longitudinal_velocity) / relative_longitudinal_velocity) : 0.0f;
  87. float longitudinal_slip_friction = settings->mLongitudinalFriction.GetValue(longitudinal_slip);
  88. // Calculate lateral friction based on slip angle
  89. float relative_velocity_len = relative_velocity.Length();
  90. float lateral_slip_angle = relative_velocity_len < 1.0e-3f? 0.0f : RadiansToDegrees(acos(min(abs(relative_longitudinal_velocity) / relative_velocity_len, 1.0f)));
  91. float lateral_slip_friction = settings->mLateralFriction.GetValue(lateral_slip_angle);
  92. // Tire friction
  93. mCombinedLongitudinalFriction = sqrt(longitudinal_slip_friction * mContactBody->GetFriction());
  94. mCombinedLateralFriction = sqrt(lateral_slip_friction * mContactBody->GetFriction());
  95. }
  96. else
  97. {
  98. // No collision
  99. mCombinedLongitudinalFriction = mCombinedLateralFriction = 0.0f;
  100. }
  101. }
  102. VehicleController *WheeledVehicleControllerSettings::ConstructController(VehicleConstraint &inConstraint) const
  103. {
  104. return new WheeledVehicleController(*this, inConstraint);
  105. }
  106. void WheeledVehicleControllerSettings::SaveBinaryState(StreamOut &inStream) const
  107. {
  108. mEngine.SaveBinaryState(inStream);
  109. mTransmission.SaveBinaryState(inStream);
  110. uint32 num_differentials = (uint32)mDifferentials.size();
  111. inStream.Write(num_differentials);
  112. for (const VehicleDifferentialSettings &d : mDifferentials)
  113. d.SaveBinaryState(inStream);
  114. }
  115. void WheeledVehicleControllerSettings::RestoreBinaryState(StreamIn &inStream)
  116. {
  117. mEngine.RestoreBinaryState(inStream);
  118. mTransmission.RestoreBinaryState(inStream);
  119. uint32 num_differentials = 0;
  120. inStream.Read(num_differentials);
  121. mDifferentials.resize(num_differentials);
  122. for (VehicleDifferentialSettings &d : mDifferentials)
  123. d.RestoreBinaryState(inStream);
  124. }
  125. WheeledVehicleController::WheeledVehicleController(const WheeledVehicleControllerSettings &inSettings, VehicleConstraint &inConstraint) :
  126. VehicleController(inConstraint)
  127. {
  128. // Copy engine settings
  129. static_cast<VehicleEngineSettings &>(mEngine) = inSettings.mEngine;
  130. JPH_ASSERT(inSettings.mEngine.mMinRPM >= 0.0f);
  131. JPH_ASSERT(inSettings.mEngine.mMinRPM <= inSettings.mEngine.mMaxRPM);
  132. // Copy transmission settings
  133. static_cast<VehicleTransmissionSettings &>(mTransmission) = inSettings.mTransmission;
  134. #ifdef JPH_ENABLE_ASSERTS
  135. for (float r : inSettings.mTransmission.mGearRatios)
  136. JPH_ASSERT(r > 0.0f);
  137. for (float r : inSettings.mTransmission.mReverseGearRatios)
  138. JPH_ASSERT(r < 0.0f);
  139. #endif // JPH_ENABLE_ASSERTS
  140. JPH_ASSERT(inSettings.mTransmission.mSwitchTime >= 0.0f);
  141. JPH_ASSERT(inSettings.mTransmission.mShiftDownRPM > 0.0f);
  142. JPH_ASSERT(inSettings.mTransmission.mMode != ETransmissionMode::Auto || inSettings.mTransmission.mShiftUpRPM < inSettings.mEngine.mMaxRPM);
  143. JPH_ASSERT(inSettings.mTransmission.mShiftUpRPM > inSettings.mTransmission.mShiftDownRPM);
  144. // Copy differential settings
  145. mDifferentials.resize(inSettings.mDifferentials.size());
  146. for (uint i = 0; i < mDifferentials.size(); ++i)
  147. {
  148. const VehicleDifferentialSettings &d = inSettings.mDifferentials[i];
  149. mDifferentials[i] = d;
  150. JPH_ASSERT(d.mDifferentialRatio > 0.0f);
  151. JPH_ASSERT(d.mLeftRightSplit >= 0.0f && d.mLeftRightSplit <= 1.0f);
  152. JPH_ASSERT(d.mEngineTorqueRatio >= 0.0f);
  153. }
  154. }
  155. void WheeledVehicleController::PreCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
  156. {
  157. JPH_PROFILE_FUNCTION();
  158. for (Wheel *w_base : mConstraint.GetWheels())
  159. {
  160. WheelWV *w = static_cast<WheelWV *>(w_base);
  161. // Set steering angle
  162. w->SetSteerAngle(-mRightInput * w->GetSettings()->mMaxSteerAngle);
  163. }
  164. }
  165. void WheeledVehicleController::PostCollide(float inDeltaTime, PhysicsSystem &inPhysicsSystem)
  166. {
  167. JPH_PROFILE_FUNCTION();
  168. Wheels &wheels = mConstraint.GetWheels();
  169. // Update wheel angle, do this before applying torque to the wheels (as friction will slow them down again)
  170. for (Wheel *w_base : wheels)
  171. {
  172. WheelWV *w = static_cast<WheelWV *>(w_base);
  173. w->Update(inDeltaTime, mConstraint);
  174. }
  175. // First calculate engine speed based on speed of all wheels
  176. bool can_engine_apply_torque = false;
  177. if (mTransmission.GetCurrentGear() != 0 && mTransmission.GetClutchFriction() > 1.0e-3f)
  178. {
  179. float transmission_ratio = mTransmission.GetCurrentRatio();
  180. bool forward = transmission_ratio >= 0.0f;
  181. float slowest_wheel_speed = forward? FLT_MAX : -FLT_MAX;
  182. for (const VehicleDifferentialSettings &d : mDifferentials)
  183. if (d.mEngineTorqueRatio > 0.0f)
  184. {
  185. if (d.mLeftWheel != -1 && d.mLeftRightSplit < 1.0f)
  186. {
  187. const Wheel *w = wheels[d.mLeftWheel];
  188. if (forward)
  189. slowest_wheel_speed = min(slowest_wheel_speed, w->GetAngularVelocity() * d.mDifferentialRatio);
  190. else
  191. slowest_wheel_speed = max(slowest_wheel_speed, w->GetAngularVelocity() * d.mDifferentialRatio);
  192. can_engine_apply_torque |= w->HasContact();
  193. }
  194. if (d.mRightWheel != -1 && d.mLeftRightSplit > 0.0f)
  195. {
  196. const Wheel *w = wheels[d.mRightWheel];
  197. if (forward)
  198. slowest_wheel_speed = min(slowest_wheel_speed, w->GetAngularVelocity() * d.mDifferentialRatio);
  199. else
  200. slowest_wheel_speed = max(slowest_wheel_speed, w->GetAngularVelocity() * d.mDifferentialRatio);
  201. can_engine_apply_torque |= w->HasContact();
  202. }
  203. }
  204. // Update RPM only if the wheels are connected to the engine
  205. if (slowest_wheel_speed > -FLT_MAX && slowest_wheel_speed < FLT_MAX)
  206. mEngine.SetCurrentRPM(slowest_wheel_speed * transmission_ratio * VehicleEngine::cAngularVelocityToRPM);
  207. mEngine.SetCurrentRPM(Clamp(mEngine.GetCurrentRPM(), mEngine.mMinRPM, mEngine.mMaxRPM));
  208. }
  209. else
  210. {
  211. // Engine not connected to wheels, update RPM based on engine inertia alone
  212. mEngine.UpdateRPM(inDeltaTime, abs(mForwardInput));
  213. }
  214. // Update transmission
  215. mTransmission.Update(inDeltaTime, mEngine.GetCurrentRPM(), mForwardInput, can_engine_apply_torque);
  216. // Calculate the amount of torque the transmission gives to the differentials
  217. float transmission_ratio = mTransmission.GetCurrentRatio();
  218. float transmission_torque = mTransmission.GetClutchFriction() * transmission_ratio * mEngine.GetTorque(abs(mForwardInput));
  219. if (transmission_torque != 0.0f)
  220. {
  221. // Calculate the max angular velocity of the differential given current engine RPM
  222. // Note this adds 0.1% slop to avoid numerical accuracy issues
  223. float differential_max_angular_velocity = mEngine.GetCurrentRPM() / (transmission_ratio * VehicleEngine::cAngularVelocityToRPM) * 1.001f;
  224. // Apply the transmission torque to the wheels
  225. for (const VehicleDifferentialSettings &d : mDifferentials)
  226. if (d.mEngineTorqueRatio > 0.0f)
  227. {
  228. // Calculate torque on this differential
  229. float differential_torque = d.mEngineTorqueRatio * d.mDifferentialRatio * transmission_torque;
  230. // Calculate max angular velocity for wheels on this differential
  231. float wheel_max_angular_velocity = differential_max_angular_velocity / d.mDifferentialRatio;
  232. // Left wheel
  233. if (d.mLeftWheel != -1 && d.mLeftRightSplit < 1.0f)
  234. {
  235. WheelWV *w = static_cast<WheelWV *>(wheels[d.mLeftWheel]);
  236. if (w->GetAngularVelocity() * wheel_max_angular_velocity < 0.0f || abs(w->GetAngularVelocity()) < abs(wheel_max_angular_velocity))
  237. {
  238. float wheel_torque = differential_torque * (1.0f - d.mLeftRightSplit);
  239. w->ApplyTorque(wheel_torque, inDeltaTime);
  240. }
  241. }
  242. // Right wheel
  243. if (d.mRightWheel != -1 && d.mLeftRightSplit > 0.0f)
  244. {
  245. WheelWV *w = static_cast<WheelWV *>(wheels[d.mRightWheel]);
  246. if (w->GetAngularVelocity() * wheel_max_angular_velocity < 0.0f || abs(w->GetAngularVelocity()) < abs(wheel_max_angular_velocity))
  247. {
  248. float wheel_torque = differential_torque * d.mLeftRightSplit;
  249. w->ApplyTorque(wheel_torque, inDeltaTime);
  250. }
  251. }
  252. }
  253. }
  254. // Braking
  255. for (Wheel *w_base : wheels)
  256. {
  257. WheelWV *w = static_cast<WheelWV *>(w_base);
  258. const WheelSettingsWV *settings = w->GetSettings();
  259. // Combine brake with hand brake torque
  260. float brake_torque = mBrakeInput * settings->mMaxBrakeTorque + mHandBrakeInput * settings->mMaxHandBrakeTorque;
  261. if (brake_torque > 0.0f)
  262. {
  263. // Calculate how much torque is needed to stop the wheels from rotating in this time step
  264. float brake_torque_to_lock_wheels = abs(w->GetAngularVelocity()) * settings->mInertia / inDeltaTime;
  265. if (brake_torque > brake_torque_to_lock_wheels)
  266. {
  267. // Wheels are locked
  268. w->SetAngularVelocity(0.0f);
  269. w->mBrakeImpulse = (brake_torque - brake_torque_to_lock_wheels) * inDeltaTime / settings->mRadius;
  270. }
  271. else
  272. {
  273. // Slow down the wheels
  274. w->ApplyTorque(-Sign(w->GetAngularVelocity()) * brake_torque, inDeltaTime);
  275. w->mBrakeImpulse = 0.0f;
  276. }
  277. }
  278. else
  279. {
  280. // Not braking
  281. w->mBrakeImpulse = 0.0f;
  282. }
  283. }
  284. }
  285. bool WheeledVehicleController::SolveLongitudinalAndLateralConstraints(float inDeltaTime)
  286. {
  287. bool impulse = false;
  288. for (Wheel *w_base : mConstraint.GetWheels())
  289. if (w_base->HasContact())
  290. {
  291. WheelWV *w = static_cast<WheelWV *>(w_base);
  292. const WheelSettingsWV *settings = w->GetSettings();
  293. // Calculate max impulse that we can apply on the ground
  294. float max_longitudinal_friction_impulse = w->mCombinedLongitudinalFriction * w->GetSuspensionLambda();
  295. // Calculate relative velocity between wheel contact point and floor in longitudinal direction
  296. Vec3 relative_velocity = mConstraint.GetVehicleBody()->GetPointVelocity(w->GetContactPosition()) - w->GetContactPointVelocity();
  297. float relative_longitudinal_velocity = relative_velocity.Dot(w->GetContactLongitudinal());
  298. // Calculate brake force to apply
  299. float min_longitudinal_impulse, max_longitudinal_impulse;
  300. if (w->mBrakeImpulse != 0.0f)
  301. {
  302. // Limit brake force by max tire friction
  303. float brake_impulse = min(w->mBrakeImpulse, max_longitudinal_friction_impulse);
  304. // Check which direction the brakes should be applied (we don't want to apply an impulse that would accelerate the vehicle)
  305. if (relative_longitudinal_velocity >= 0.0f)
  306. {
  307. min_longitudinal_impulse = -brake_impulse;
  308. max_longitudinal_impulse = 0.0f;
  309. }
  310. else
  311. {
  312. min_longitudinal_impulse = 0.0f;
  313. max_longitudinal_impulse = brake_impulse;
  314. }
  315. // 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
  316. impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);
  317. }
  318. else
  319. {
  320. // Assume we want to apply an angular impulse that makes the delta velocity between wheel and ground zero in one time step, calculate the amount of linear impulse needed to do that
  321. float desired_angular_velocity = relative_longitudinal_velocity / settings->mRadius;
  322. float linear_impulse = (w->GetAngularVelocity() - desired_angular_velocity) * settings->mInertia / settings->mRadius;
  323. // Limit the impulse by max tire friction
  324. min_longitudinal_impulse = max_longitudinal_impulse = w->GetLongitudinalLambda() + Sign(linear_impulse) * min(abs(linear_impulse), max_longitudinal_friction_impulse);
  325. // Longitudinal impulse
  326. float prev_lambda = w->GetLongitudinalLambda();
  327. impulse |= w->SolveLongitudinalConstraintPart(mConstraint, min_longitudinal_impulse, max_longitudinal_impulse);
  328. // Update the angular velocity of the wheels according to the lambda that was applied
  329. w->SetAngularVelocity(w->GetAngularVelocity() - (w->GetLongitudinalLambda() - prev_lambda) * settings->mRadius / settings->mInertia);
  330. }
  331. }
  332. for (Wheel *w_base : mConstraint.GetWheels())
  333. if (w_base->HasContact())
  334. {
  335. WheelWV *w = static_cast<WheelWV *>(w_base);
  336. // Lateral friction
  337. float max_lateral_friction_impulse = w->mCombinedLateralFriction * w->GetSuspensionLambda();
  338. impulse |= w->SolveLateralConstraintPart(mConstraint, -max_lateral_friction_impulse, max_lateral_friction_impulse);
  339. }
  340. return impulse;
  341. }
  342. #ifdef JPH_DEBUG_RENDERER
  343. void WheeledVehicleController::Draw(DebugRenderer *inRenderer) const
  344. {
  345. // Draw RPM
  346. Body *body = mConstraint.GetVehicleBody();
  347. Vec3 rpm_meter_up = body->GetRotation() * mConstraint.GetLocalUp();
  348. Vec3 rpm_meter_pos = body->GetPosition() + body->GetRotation() * mRPMMeterPosition;
  349. Vec3 rpm_meter_fwd = body->GetRotation() * mConstraint.GetLocalForward();
  350. mEngine.DrawRPM(inRenderer, rpm_meter_pos, rpm_meter_fwd, rpm_meter_up, mRPMMeterSize, mTransmission.mShiftDownRPM, mTransmission.mShiftUpRPM);
  351. // Draw current vehicle state
  352. string status = StringFormat("Forward: %.1f, Right: %.1f, Brake: %.1f, HandBrake: %.1f\n"
  353. "Gear: %d, Clutch: %.1f, EngineRPM: %.0f, V: %.1f km/h",
  354. (double)mForwardInput, (double)mRightInput, (double)mBrakeInput, (double)mHandBrakeInput,
  355. mTransmission.GetCurrentGear(), (double)mTransmission.GetClutchFriction(), (double)mEngine.GetCurrentRPM(), (double)body->GetLinearVelocity().Length() * 3.6);
  356. inRenderer->DrawText3D(body->GetPosition(), status, Color::sWhite, mConstraint.GetDrawConstraintSize());
  357. for (const Wheel *w_base : mConstraint.GetWheels())
  358. {
  359. const WheelWV *w = static_cast<const WheelWV *>(w_base);
  360. const WheelSettings *settings = w->GetSettings();
  361. // Calculate where the suspension attaches to the body in world space
  362. Vec3 ws_position = body->GetCenterOfMassPosition() + body->GetRotation() * (settings->mPosition - body->GetShape()->GetCenterOfMass());
  363. if (w->HasContact())
  364. {
  365. // Draw contact
  366. inRenderer->DrawLine(ws_position, w->GetContactPosition(), w->HasHitHardPoint()? Color::sRed : Color::sGreen); // Red if we hit the 'max up' limit
  367. inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactNormal(), Color::sYellow);
  368. inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLongitudinal(), Color::sRed);
  369. inRenderer->DrawLine(w->GetContactPosition(), w->GetContactPosition() + w->GetContactLateral(), Color::sBlue);
  370. DebugRenderer::sInstance->DrawText3D(w->GetContactPosition(), StringFormat("W: %.1f, S: %.2f, FrLateral: %.1f, FrLong: %.1f", (double)w->GetAngularVelocity(), (double)w->GetSuspensionLength(), (double)w->mCombinedLateralFriction, (double)w->mCombinedLongitudinalFriction), Color::sWhite, 0.1f);
  371. }
  372. else
  373. {
  374. // Draw 'no hit'
  375. Vec3 max_droop = body->GetRotation() * settings->mDirection * (settings->mSuspensionMaxLength + settings->mRadius);
  376. inRenderer->DrawLine(ws_position, ws_position + max_droop, Color::sYellow);
  377. DebugRenderer::sInstance->DrawText3D(ws_position + max_droop, StringFormat("W: %.1f", (double)w->GetAngularVelocity()), Color::sRed, 0.1f);
  378. }
  379. }
  380. }
  381. #endif // JPH_DEBUG_RENDERER
  382. void WheeledVehicleController::SaveState(StateRecorder &inStream) const
  383. {
  384. inStream.Write(mForwardInput);
  385. inStream.Write(mRightInput);
  386. inStream.Write(mBrakeInput);
  387. inStream.Write(mHandBrakeInput);
  388. mEngine.SaveState(inStream);
  389. mTransmission.SaveState(inStream);
  390. }
  391. void WheeledVehicleController::RestoreState(StateRecorder &inStream)
  392. {
  393. inStream.Read(mForwardInput);
  394. inStream.Read(mRightInput);
  395. inStream.Read(mBrakeInput);
  396. inStream.Read(mHandBrakeInput);
  397. mEngine.RestoreState(inStream);
  398. mTransmission.RestoreState(inStream);
  399. }
  400. } // JPH