WheeledVehicleController.cpp 19 KB

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