How to Calculate Tire Slip Angle in Matter.js
This article explains how to simulate realistic vehicle handling in Matter.js by calculating tire slip angles and applying corrective lateral grip forces. You will learn the underlying physics of tire slip, how to decompose a wheel body's velocity into local coordinate axes, how to derive the slip angle using trigonometry, and how to apply the resulting restoring forces to prevent unrealistic sliding in a 2D physics simulation.
Understanding Tire Slip Angle
In real-world vehicle dynamics, a tire does not immediately travel in the exact direction it is pointed during a turn. The mechanical deformation of the tire rubber creates a disparity between the wheel's pointing direction (heading vector) and its actual path of travel (velocity vector). This difference is the slip angle (\(\alpha\)).
The slip angle generates a perpendicular restoring force known as cornering force. This force resists lateral skidding and pushes the vehicle toward its steering direction. Without modeling slip angle in Matter.js, vehicles tend to slide like ice pucks or experience jittery lateral snapping if you simply cancel lateral velocity directly.
Step 1: Define Wheel Vectors
To determine the slip angle, first resolve the wheel body's
orientation into unit vectors representing its forward (longitudinal)
and sideways (lateral) directions based on wheel.angle:
const forwardVector = {
x: Math.cos(wheel.angle),
y: Math.sin(wheel.angle)
};
const lateralVector = {
x: -Math.sin(wheel.angle),
y: Math.cos(wheel.angle)
};Step 2: Decompose Wheel Velocity
Matter.js provides global velocities (wheel.velocity.x
and wheel.velocity.y). Use dot products to project this
global velocity vector onto the wheel’s local axes:
const velocity = wheel.velocity;
// Project velocity onto forward and lateral vectors
const forwardVelocity = (velocity.x * forwardVector.x) + (velocity.y * forwardVector.y);
const lateralVelocity = (velocity.x * lateralVector.x) + (velocity.y * lateralVector.y);Step 3: Compute the Slip Angle
The slip angle \(\alpha\) is calculated as the arctangent of lateral velocity over the absolute forward velocity:
// Add a small epsilon to avoid division by zero when the car is stationary
const epsilon = 0.0001;
const slipAngle = Math.atan2(lateralVelocity, Math.abs(forwardVelocity) + epsilon);Using Math.abs(forwardVelocity) ensures that reversing
does not invert the steering geometry unintentionally.
Step 4: Calculate the Restoring Cornering Force
At low to moderate slip angles, lateral force is roughly linear and proportional to the slip angle multiplied by a cornering stiffness coefficient (\(C_\alpha\)). At high slip angles, the tire reaches peak friction and begins to slide.
You can approximate this with a simplified linear model clamped by maximum grip:
// Cornering stiffness constant
const corneringStiffness = 0.5;
const maxGrip = 0.05; // Maximum available lateral force
// Linear restoring force (acts opposite to lateral slide)
let lateralForceMagnitude = -slipAngle * corneringStiffness;
// Clamp force to represent the limit of adhesion (friction circle)
lateralForceMagnitude = Math.max(-maxGrip, Math.min(maxGrip, lateralForceMagnitude));Step 5: Apply the Force in Matter.js
Convert the scalar force magnitude back into a world-space vector
along the lateral axis and apply it at the center of the wheel body
using Matter.Body.applyForce:
const restoringForce = {
x: lateralVector.x * lateralForceMagnitude,
y: lateralVector.y * lateralForceMagnitude
};
// Apply force inside the 'beforeUpdate' engine event loop
Matter.Body.applyForce(wheel, wheel.position, restoringForce);Integrating into the Simulation Loop
Attach this calculation to the Matter.js beforeUpdate
event so that forces are updated consistently before the physics engine
integrates movement:
Matter.Events.on(engine, 'beforeUpdate', () => {
wheels.forEach(wheel => {
const forwardVector = { x: Math.cos(wheel.angle), y: Math.sin(wheel.angle) };
const lateralVector = { x: -Math.sin(wheel.angle), y: Math.cos(wheel.angle) };
const forwardVelocity = (wheel.velocity.x * forwardVector.x) + (wheel.velocity.y * forwardVector.y);
const lateralVelocity = (wheel.velocity.x * lateralVector.x) + (wheel.velocity.y * lateralVector.y);
const slipAngle = Math.atan2(lateralVelocity, Math.abs(forwardVelocity) + 0.001);
const corneringStiffness = 0.15;
const maxGrip = 0.02;
let lateralForceMagnitude = -slipAngle * corneringStiffness;
lateralForceMagnitude = Math.max(-maxGrip, Math.min(maxGrip, lateralForceMagnitude));
Matter.Body.applyForce(wheel, wheel.position, {
x: lateralVector.x * lateralForceMagnitude,
y: lateralVector.y * lateralForceMagnitude
});
});
});Tuning corneringStiffness adjusts how sharply the
vehicle responds to steering input, while maxGrip dictates
when the vehicle breaks traction and transitions into a drift.