Frictionless Ice Patches in Matter.js
Creating realistic ice patches in Matter.js that preserve momentum indefinitely requires overcoming both surface contact friction and the physics engine's default air resistance. While reducing a surface's friction to zero is intuitive, Matter.js bodies will naturally decelerate due to ambient air drag unless specifically configured. This guide demonstrates how to build zero-friction zones by combining collision filtering, zero-friction properties, and dynamic air-resistance toggling using Matter.js collision events.
The Challenge with Default Physics
Matter.js calculates contact friction between two colliding bodies by
finding the minimum friction value between them using the formula
Math.min(bodyA.friction, bodyB.friction). Setting the ice
patch's friction to 0 successfully removes surface
resistance.
However, all rigid bodies in Matter.js are assigned a default
frictionAir value of 0.01. Even if surface
friction is completely eliminated, this ambient drag causes moving
objects to eventually slow down and stop. To preserve momentum
indefinitely, you must dynamically negate frictionAir for
as long as the body remains in contact with the ice.
Step 1: Create the Ice Surface
Define the ice patch with friction,
frictionStatic, and restitution set to
eliminate all surface drag:
const icePatch = Matter.Bodies.rectangle(400, 500, 300, 20, {
isStatic: true,
friction: 0,
frictionStatic: 0,
label: 'icePatch'
});
Matter.Composite.add(engine.world, icePatch);Step 2: Handle Air Drag via Collision Events
To keep momentum constant, listen for collision events on the engine.
When a body enters the ice patch, save its original
frictionAir value and set it to 0. When the
body leaves the patch, restore its original drag.
// Track initial drag and set frictionAir to 0 on contact
Matter.Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
const { bodyA, bodyB } = pair;
if (bodyA.label === 'icePatch' || bodyB.label === 'icePatch') {
const dynamicBody = bodyA.label === 'icePatch' ? bodyB : bodyA;
if (!dynamicBody.isStatic) {
// Store the original frictionAir if not already saved
if (dynamicBody.plugin === undefined) dynamicBody.plugin = {};
if (dynamicBody.plugin.originalFrictionAir === undefined) {
dynamicBody.plugin.originalFrictionAir = dynamicBody.frictionAir;
}
dynamicBody.frictionAir = 0;
dynamicBody.friction = 0;
}
}
});
});
// Restore original drag when exiting the ice
Matter.Events.on(engine, 'collisionEnd', (event) => {
event.pairs.forEach((pair) => {
const { bodyA, bodyB } = pair;
if (bodyA.label === 'icePatch' || bodyB.label === 'icePatch') {
const dynamicBody = bodyA.label === 'icePatch' ? bodyB : bodyA;
if (!dynamicBody.isStatic && dynamicBody.plugin?.originalFrictionAir !== undefined) {
dynamicBody.frictionAir = dynamicBody.plugin.originalFrictionAir;
dynamicBody.friction = 0.1; // Restore default or intended surface friction
delete dynamicBody.plugin.originalFrictionAir;
}
}
});
});Step 3: Prevent Angular Deceleration (Optional)
If you also want the object to spin infinitely while sliding across
the ice, set its angular velocity retention by removing rotational
damping. Alongside setting frictionAir to 0,
you can store and clear rotational resistance by overriding angular
damping in a beforeUpdate event or ensuring no torque is
applied to the sliding object.
By neutralizing both surface friction and air friction during collision states, bodies entering the ice surface will maintain their exact linear velocity until they exit the patch or collide with another obstacle.