Give the vehicles real wheels #111

Closed
opened 2026-10-20 23:40:40 +00:00 by sermuns · 2 comments
Owner

The vehicles are a kinematic model, and they show it.

vehicle::step_vehicle integrates a speed, turns a heading, and moves the
vehicle along a line. The drift model on top of it — velocity_dir versus
yaw, the slip angle, DRIFT_MAX_SLIP — is a hand-rolled approximation of
what a tyre does. The result is a scooter that slides sideways across a
campus without ever leaning, a bus that corners like a shopping trolley, and
a bicycle that cannot fall over. Nothing on the campus can hit anything, and
nothing can be hit.

Rapier ships a ray-cast vehicle controller
(rapier3d::control::DynamicRayCastVehicleController, ported from Bullet's
btRaycastVehicle): a chassis rigid body, wheels that ray-cast down to find
the ground, a suspension that compresses over bumps, and tyre forces that
decide what the vehicle does with the grip it has.

What that changes

  • Lean is no longer a drawing. The lean the avatar does and the lean the
    vehicle does are the same number, because the vehicle actually rolls. A
    scooter leans into a corner because the chassis is tipping.
  • Rollover is possible. A bus that takes a corner too fast goes over. So
    does a scooter that hits a kerb sideways. Neither can happen today.
  • The drift model can go. DRIFT_MAX_SLIP, DRIFT_SLIDE_RATE,
    GRIP_REALIGN_RATE and drift_turn_rate exist to fake what a tyre does
    with lateral force. A real wheel does it, and the hand-rolled constants go
    away with the code that reads them.
  • The ground is a real surface. The Mobility::GROUND_FLOOR support
    height under the vehicle becomes the ray the wheel casts. A scooter on the
    hill finds the hill; a scooter half on a kerb rides up it.
  • Things get hit. A vehicle meets a player and the player is moved. That
    is a separate issue (the ragdoll), but it needs this one first.

Where it fits

The vehicles are server-authoritative today — the server steps them and
sends VehiclePose at 20 Hz — and that stays. The physics world lives on the
server; the client keeps its local vehicle as the stand-in it already has
(Vehicle::from_pose).

That means VehiclePose grows fields, and the wire contract is append-only:
new fields go at the end of the struct, and ClientMsg / ServerMsg variants
are appended, never reordered. The fields worth carrying are the ones a client
cannot derive from pos and yaw: the roll and pitch of the chassis, the
suspension compression per wheel, and whether the wheels are on the ground at
all. Those last two are what a client needs to draw a wheel that is off the
ground.

The cost

Rapier's vehicle controller is a Bullet port and it is a chassis: one rigid
body with ray-cast wheels, not four wheels with their own rigid bodies. That
is the right shape for a scooter, but a bus wants a body that leans and a
suspension that does not bottom out, which is more than a ray-cast wheel
gives. Expect to hand-tune the WheelTuning per VehicleKind — the
VehicleKind enum already owns eye_height, max_speed, acceleration,
braking, turn_rate, steer_max and drift_turn_rate, and it would grow
the suspension and grip constants next to those.

DynamicRayCastVehicleController lives in rapier3d::control, which is
not in the crate's prelude. It is an explicit import.

Related

  • #110 the physics engine itself is the blocker for all of this.
  • #114 the ragdoll is what happens when a vehicle reaches a player.
  • The drift model it replaces is the one that left rubber and smoke behind.
  • The rig for the bus — driver, six seats, the cabin panels — is already
    drawn, so the seat positions ride along unchanged.
The vehicles are a kinematic model, and they show it. `vehicle::step_vehicle` integrates a speed, turns a heading, and moves the vehicle along a line. The drift model on top of it — `velocity_dir` versus `yaw`, the slip angle, `DRIFT_MAX_SLIP` — is a hand-rolled approximation of what a tyre does. The result is a scooter that slides sideways across a campus without ever leaning, a bus that corners like a shopping trolley, and a bicycle that cannot fall over. Nothing on the campus can hit anything, and nothing can be hit. Rapier ships a ray-cast vehicle controller (`rapier3d::control::DynamicRayCastVehicleController`, ported from Bullet's `btRaycastVehicle`): a chassis rigid body, wheels that ray-cast down to find the ground, a suspension that compresses over bumps, and tyre forces that decide what the vehicle does with the grip it has. **What that changes** - **Lean is no longer a drawing.** The lean the avatar does and the lean the vehicle does are the same number, because the vehicle actually rolls. A scooter leans into a corner because the chassis is tipping. - **Rollover is possible.** A bus that takes a corner too fast goes over. So does a scooter that hits a kerb sideways. Neither can happen today. - **The drift model can go.** `DRIFT_MAX_SLIP`, `DRIFT_SLIDE_RATE`, `GRIP_REALIGN_RATE` and `drift_turn_rate` exist to fake what a tyre does with lateral force. A real wheel does it, and the hand-rolled constants go away with the code that reads them. - **The ground is a real surface.** The `Mobility::GROUND_FLOOR` support height under the vehicle becomes the ray the wheel casts. A scooter on the hill finds the hill; a scooter half on a kerb rides up it. - **Things get hit.** A vehicle meets a player and the player is moved. That is a separate issue (the ragdoll), but it needs this one first. **Where it fits** The vehicles are **server-authoritative** today — the server steps them and sends `VehiclePose` at 20 Hz — and that stays. The physics world lives on the server; the client keeps its local vehicle as the stand-in it already has (`Vehicle::from_pose`). That means `VehiclePose` grows fields, and the wire contract is append-only: new fields go at the end of the struct, and `ClientMsg` / `ServerMsg` variants are appended, never reordered. The fields worth carrying are the ones a client cannot derive from `pos` and `yaw`: the roll and pitch of the chassis, the suspension compression per wheel, and whether the wheels are on the ground at all. Those last two are what a client needs to draw a wheel that is off the ground. **The cost** Rapier's vehicle controller is a Bullet port and it is a *chassis*: one rigid body with ray-cast wheels, not four wheels with their own rigid bodies. That is the right shape for a scooter, but a bus wants a body that leans and a suspension that does not bottom out, which is more than a ray-cast wheel gives. Expect to hand-tune the `WheelTuning` per `VehicleKind` — the `VehicleKind` enum already owns `eye_height`, `max_speed`, `acceleration`, `braking`, `turn_rate`, `steer_max` and `drift_turn_rate`, and it would grow the suspension and grip constants next to those. `DynamicRayCastVehicleController` lives in `rapier3d::control`, which is **not** in the crate's prelude. It is an explicit import. **Related** - #110 the physics engine itself is the blocker for all of this. - #114 the ragdoll is what happens when a vehicle reaches a player. - The drift model it replaces is the one that left rubber and smoke behind. - The rig for the bus — driver, six seats, the cabin panels — is already drawn, so the seat positions ride along unchanged.
sermuns added this to the Play milestone 2026-10-20 23:40:40 +00:00
Author
Owner

The wheels are drawn now. The physics is not.

Vehicles have real rolling wheels: tires and rims as their own meshes on per-kind axles, with the roll phase advanced by the post-collision travel so they match the ground. The ground under a vehicle is a real surface too, with a per-kind speed and grip answer for ice, soft ground and pavement.

What is left is the part this issue is actually about: the lean is still drawn rather than simulated, the drift model is still the hand-rolled speed-and-heading one, nothing can tip over, and nothing on the campus can be hit. So the issue stays a physics issue; what has landed is the presentation a real wheel would have to replace.

The wheels are drawn now. The physics is not. Vehicles have real rolling wheels: tires and rims as their own meshes on per-kind axles, with the roll phase advanced by the post-collision travel so they match the ground. The ground under a vehicle is a real surface too, with a per-kind speed and grip answer for ice, soft ground and pavement. What is left is the part this issue is actually about: the lean is still drawn rather than simulated, the drift model is still the hand-rolled speed-and-heading one, nothing can tip over, and nothing on the campus can be hit. So the issue stays a physics issue; what has landed is the presentation a real wheel would have to replace.
Author
Owner

The physics half of this is not in scope right now, and what is here is close enough to close the issue out.

What landed is the presentation a real wheel would need to replace: tires and rims as their own meshes on per-kind axles, the roll phase advanced by post-collision travel so they match the ground, and a real surface under the vehicle with a per-kind speed and grip answer for ice, soft ground and pavement.

What is left is the simulation the issue is actually about — a chassis that leans instead of a drawn lean, a tyre model instead of the hand-rolled speed-and-heading drift, rollover, and vehicles meeting players. That rides on the physics engine in #110, which is out of scope for now.

Closing as close-enough. Reopen if the simulated half comes back on the table.

The physics half of this is not in scope right now, and what is here is close enough to close the issue out. What landed is the presentation a real wheel would need to replace: tires and rims as their own meshes on per-kind axles, the roll phase advanced by post-collision travel so they match the ground, and a real surface under the vehicle with a per-kind speed and grip answer for ice, soft ground and pavement. What is left is the simulation the issue is actually about — a chassis that leans instead of a drawn lean, a tyre model instead of the hand-rolled speed-and-heading drift, rollover, and vehicles meeting players. That rides on the physics engine in #110, which is out of scope for now. Closing as close-enough. Reopen if the simulated half comes back on the table.
Sign in to join this conversation.
No description provided.