Skip to main content

vehicle_controller

Simulating a car with rigid-bodies and joints, for example using one rigid-body per wheel attached to the chassis by a joint, is possible but can be difficult to control for games requiring non-realistic vehicles. This is why Rapier provides a vehicle controller (the current implementation was ported from the btRaycastVehicle of Bullet). The vehicle is a single rigid-body modeling its chassis, and its wheels are only represented by ray-casts pushing that body along a spring-like suspension.

Setup​

The chassis is created like any other dynamic rigid-body, and the wheels are added to the controller afterwards. Each wheel is given its position on the chassis, the direction of its suspension (the direction of its ray-cast), its axle, the rest length of its suspension, and its radius. The WheelTuning shared by the wheels is what makes the vehicle feel heavy or light by controlling the elastic properties (stiffness and damping) of the suspension, as well as the grip of the wheels:

// The chassis is an ordinary dynamic rigid-body.
let hw = 0.3;
let hh = 0.15;
let chassis = world.createRigidBody(
RAPIER.RigidBodyDesc.dynamic().setTranslation(0.0, 1.0, 0.0),
);
world.createCollider(RAPIER.ColliderDesc.cuboid(hw * 2.0, hh, hw).setDensity(100.0), chassis);

let vehicle = world.createVehicleController(chassis);
let wheelPositions = [
{ x: hw * 1.5, y: -hh, z: hw }, { x: hw * 1.5, y: -hh, z: -hw },
{ x: -hw * 1.5, y: -hh, z: hw }, { x: -hw * 1.5, y: -hh, z: -hw },
];

for (let position of wheelPositions) {
// The position of the wheel, the direction its suspension pushes along, its axle, the
// rest length of its suspension, and its radius; all in the local frame of the chassis.
vehicle.addWheel(position, { x: 0.0, y: -1.0, z: 0.0 }, { x: 0.0, y: 0.0, z: 1.0 }, hh, hh / 4.0);
}

// The tuning of each wheel: its suspension and its grip.
for (let i = 0; i < vehicle.numWheels(); ++i) {
vehicle.setWheelSuspensionStiffness(i, 100.0);
vehicle.setWheelSuspensionCompression(i, 10.0);
vehicle.setWheelSuspensionRelaxation(i, 10.0);
}

Driving the vehicle​

A vehicle is driven by giving each of its wheels an engine force, a brake force, and a steering angle. The controller is then updated before each timestep, which is when the ray-casts are made and when the resulting suspension and friction forces are applied to the chassis. It is strongly recommended to exclude the chassis from the ray-casts, otherwise the ray might hit it and be misinterpreted as being the floor.

for (let k = 0; k < 200; ++k) {
// The vehicle is driven by setting the engine force, the brake, and the steering angle of
// its wheels. Here the two front wheels are the driving and steering ones.
vehicle.setWheelEngineForce(0, 30.0);
vehicle.setWheelSteering(0, 0.2);
vehicle.setWheelEngineForce(1, 30.0);
vehicle.setWheelSteering(1, 0.2);

// The wheels are ray-casted against the scene: the dynamic bodies, including the chassis
// itself, are generally excluded from these ray-casts.
vehicle.updateVehicle(world.integrationParameters.dt, RAPIER.QueryFilterFlags.EXCLUDE_DYNAMIC);
world.step();
}

console.log("Vehicle speed:", vehicle.currentVehicleSpeed());
note

The state of each wheel after an update (whether it touches the floor, the compression of its suspension, its rotation angle, etc.) is readable from the controller. This is what the rendering of the wheels can be based on since there is no actual per-wheel rigid-bodies to read their state from.