|
| 1 | +--[=[ |
| 2 | + Simulate wheel on a wheel-based vehicle. |
| 3 | +
|
| 4 | + @client |
| 5 | + @class WheelVehiclePacejkaWheelClient |
| 6 | +]=] |
| 7 | + |
| 8 | +local require = require(script.Parent.loader).load(script) |
| 9 | + |
| 10 | +local BaseObject = require("BaseObject") |
| 11 | +local WheelVehicleTypes = require("WheelVehicleTypes") |
| 12 | +local WheelVehicleUtils = require("WheelVehicleUtils") |
| 13 | +local WheelVehicleWheelClient = require("WheelVehicleWheelClient") |
| 14 | + |
| 15 | +local PacejkaWheel = setmetatable({}, BaseObject) |
| 16 | +PacejkaWheel.ClassName = "PacejkaWheel" |
| 17 | +PacejkaWheel.__index = PacejkaWheel |
| 18 | + |
| 19 | +export type PacejkaWheel = |
| 20 | + typeof(setmetatable({} :: {}, {} :: typeof({ __index = PacejkaWheel }))) |
| 21 | + & WheelVehicleWheelClient.Wheel |
| 22 | + |
| 23 | +function PacejkaWheel.new( |
| 24 | + wheelObj: BasePart, |
| 25 | + wheelConfig: WheelVehicleTypes.WheelConfig, |
| 26 | + links: WheelVehicleUtils.Links, |
| 27 | + forceAttachment: Attachment |
| 28 | +): PacejkaWheel |
| 29 | + local self = setmetatable(WheelVehicleWheelClient.new(wheelObj, wheelConfig, links, forceAttachment), PacejkaWheel) |
| 30 | + |
| 31 | + return self |
| 32 | +end |
| 33 | + |
| 34 | +function PacejkaWheel.UpdateStepped(self: PacejkaWheel, deltaTimeSim: number, groundForce: number) |
| 35 | + debug.profilebegin("WheelVehiclePacejkaWheelClient:UpdateStepped") |
| 36 | + |
| 37 | + local wheelOrientation = self._wheelLink.WorldOrientation |
| 38 | + local velocityGlobal = self._obj.Velocity |
| 39 | + |
| 40 | + -- NOTE: Remember for whatever strange reason, the negative Z axis is the forward direction in Roblox. |
| 41 | + -- Here: -Z -> backward, +X -> left |
| 42 | + local velocityRelative = |
| 43 | + CFrame.fromOrientation(math.rad(wheelOrientation.X), math.rad(wheelOrientation.Y), math.rad(wheelOrientation.Z)) |
| 44 | + :VectorToObjectSpace(velocityGlobal) -- * CFrame.lookAt(Vector3.new(0, 0, 0), -velocityGlobal.Unit):Inverse() |
| 45 | + |
| 46 | + -- Do nothing if NaN velocity. |
| 47 | + if not (velocityRelative.Z == velocityRelative.Z) then |
| 48 | + return |
| 49 | + end |
| 50 | + |
| 51 | + self.rotation += -velocityRelative.Z * deltaTimeSim / (self.diameter * math.pi) * 360 |
| 52 | + if (self.rotation :: number) > 360 then |
| 53 | + self.rotation -= 360 |
| 54 | + elseif (self.rotation :: number) < 0 then |
| 55 | + self.rotation += 360 |
| 56 | + end |
| 57 | + |
| 58 | + self.attachment.Orientation = Vector3.new(self.rotation, 0, 0) |
| 59 | + |
| 60 | + -- TODO: A more advanced traction model required. |
| 61 | + local maxTraction = self._config.baseAdhesionCoefficient * groundForce |
| 62 | + self.force.Force = Vector3.new(math.clamp(-velocityRelative.X * 100, -maxTraction, maxTraction), 0, 0) |
| 63 | + |
| 64 | + debug.profileend() |
| 65 | +end |
| 66 | + |
| 67 | +return PacejkaWheel |
0 commit comments