How to Model Anisotropic Friction in Matter.js
This article explains how to simulate anisotropic friction—resisting lateral sliding while allowing smooth forward motion—within the Matter.js 2D physics engine. Because Matter.js relies on standard, isotropic Coulomb friction by default, achieving directional friction requires intercepting the physics update loop. By decomposing a body's velocity into local longitudinal and lateral vectors, you can apply custom corrective impulses or forces to damp sideways drift, making it ideal for modeling vehicles, ice skates, or skis.
The Limitation of Native Friction
Matter.js bodies use scalar properties for friction:
friction, frictionAir, and
frictionStatic. These values apply resistance uniformly in
all directions. To model objects like wheels or skate blades, the
physics engine needs to treat the object's forward axis differently from
its perpendicular (sideways) axis.
The Vector Decomposition Approach
To simulate anisotropic friction, hook into the
beforeUpdate event of the Matter.js engine. The process
involves three steps:
- Determine the body’s forward and perpendicular (lateral) unit vectors using its current rotation angle.
- Project the body's current linear velocity onto both vectors to separate forward speed from lateral drift.
- Apply an opposing force or directly scale down the lateral velocity component before the collision and position updates take place.
Implementation
Below is a complete implementation demonstrating how to damp lateral velocity on a rigid body:
const { Engine, Render, Runner, Bodies, Composite, Events, Vector } = Matter;
// Initialize engine and world
const engine = Engine.create();
const world = engine.world;
// Disable gravity for a top-down perspective
engine.gravity.y = 0;
// Create a body (e.g., a car or skate blade)
const vehicle = Bodies.rectangle(400, 300, 40, 80, {
angle: 0,
frictionAir: 0.01 // Low default air resistance
});
Composite.add(world, vehicle);
// Define friction coefficients
const lateralFriction = 0.90; // Higher value = less sideways sliding (0 to 1)
const forwardFriction = 0.01; // Longitudinal resistance
Events.on(engine, 'beforeUpdate', () => {
// 1. Calculate local directional unit vectors
const angle = vehicle.angle;
// Forward vector (assuming the body's local forward points along its length)
const forwardVector = {
x: Math.sin(angle),
y: -Math.cos(angle)
};
// Right (lateral) vector perpendicular to the forward vector
const rightVector = {
x: Math.cos(angle),
y: Math.sin(angle)
};
// 2. Project current velocity onto both axes (dot product)
const currentVelocity = vehicle.velocity;
const forwardSpeed = Vector.dot(currentVelocity, forwardVector);
const lateralSpeed = Vector.dot(currentVelocity, rightVector);
// 3. Apply damping factors
const newForwardSpeed = forwardSpeed * (1 - forwardFriction);
const newLateralSpeed = lateralSpeed * (1 - lateralFriction);
// Reconstruct the modified velocity vector
const newVelocity = {
x: (forwardVector.x * newForwardSpeed) + (rightVector.x * newLateralSpeed),
y: (forwardVector.y * newForwardSpeed) + (rightVector.y * newLateralSpeed)
};
// Update the body's velocity directly
Matter.Body.setVelocity(vehicle, newVelocity);
});Tuning Drift and Grip
- Lateral Grip (
lateralFriction): Setting this close to1.0eliminates sideways drift almost completely, acting like an ice skate in a groove or a wheel with high lateral grip. Values between0.80and0.95provide a realistic car-drifting effect. - Rolling Resistance (
forwardFriction): Keep this value low (e.g.,0.005to0.02) to allow the body to glide forward freely while still eventually coming to a stop. - Rotational Friction: If the body spins too easily
while sliding, apply proportional rotational damping using
Matter.Body.setAngularVelocity(vehicle, vehicle.angularVelocity * dampingFactor)inside the same update loop.