Simulating Friction on Inclined Planes in Matter.js
This article explains how to model and test friction coefficients on an inclined plane with variable angles using the Matter.js 2D physics engine. It covers the underlying physics of static and kinetic friction on slopes, walks through configuring the Matter.js engine and bodies, demonstrates how to dynamically change the plane's angle, and provides a complete implementation code snippet.
The Physics of the Inclined Plane
When an object rests on a ramp inclined at an angle \(\theta\), gravity pulls it down the slope with a force proportional to \(\sin(\theta)\), while normal force presses it against the surface proportional to \(\cos(\theta)\).
Sliding begins at the critical angle \(\theta_c\), defined by:
\[\tan(\theta_c) = \mu_s\]
Where \(\mu_s\) is the coefficient of static friction. Once the angle exceeds \(\theta_c\), static friction is overcome, and the block accelerates down the ramp governed by the coefficient of kinetic friction (\(\mu_k\)).
Key Matter.js Friction Properties
Matter.js models surface resistance using properties defined on rigid bodies:
friction: Defines kinetic friction during sliding. Ranges from0(frictionless) to1(high friction).frictionStatic: Defines the resistance required to initiate motion. Setting this higher thanfrictioncreates realistic "sticking" behavior.- Pair Friction Rule: By default, Matter.js
calculates the friction between two colliding bodies using
Math.min(bodyA.friction, bodyB.friction). To properly test the block's friction against a standard surface, set the ramp's friction properties to1.0or match them explicitly.
Implementation Steps
1. Setup Engine and World
Initialize the core Matter.js modules:
const { Engine, Render, Runner, Bodies, Composite, Body } = Matter;
const engine = Engine.create();
const world = engine.world;
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false
}
});
Render.run(render);
Runner.run(Runner.create(), engine);2. Create the Plane and the Test Block
Create the ramp as a static body and the block as a dynamic body placed on top of it.
const rampWidth = 500;
const rampHeight = 20;
const rampX = 400;
const rampY = 350;
// Static ramp
const ramp = Bodies.rectangle(rampX, rampY, rampWidth, rampHeight, {
isStatic: true,
friction: 1.0,
frictionStatic: 1.0,
render: { fillStyle: '#555' }
});
// Dynamic block to test friction
const block = Bodies.rectangle(300, 200, 40, 40, {
friction: 0.3, // Kinetic friction
frictionStatic: 0.5, // Static friction (critical angle ~ 26.5°)
restitution: 0.0, // Prevent bouncing
render: { fillStyle: '#e74c3c' }
});
Composite.add(world, [ramp, block]);3. Adjusting the Angle Dynamically
To demonstrate variable angles, update the ramp's angle using
Body.setAngle(). To prevent unnatural collisions when
adjusting an already active simulation, reset the block position when
the angle changes:
function setInclineAngle(degrees) {
const radians = (degrees * Math.PI) / 180;
// Rotate ramp around its center
Body.setAngle(ramp, radians);
// Calculate position along the rotated surface to reset the test block
const offset = -150; // Distance from center up the ramp
const spawnX = rampX + offset * Math.cos(radians) - (rampHeight / 2 + 20) * Math.sin(radians);
const spawnY = rampY + offset * Math.sin(radians) + (rampHeight / 2 + 20) * Math.cos(radians);
Body.setPosition(block, { x: spawnX, y: spawnY });
Body.setAngle(block, radians);
Body.setVelocity(block, { x: 0, y: 0 });
Body.setAngularVelocity(block, 0);
}4. Connecting User Controls
Hook the function up to an HTML range slider to let users continuously vary the angle and observe the threshold where static friction gives way to motion:
<label for="angleSlider">Ramp Angle: <span id="angleValue">0</span>°</label>
<input id="angleSlider" type="range" min="0" max="60" value="0" step="1">
<script>
const slider = document.getElementById('angleSlider');
const display = document.getElementById('angleValue');
slider.addEventListener('input', (event) => {
const angle = parseFloat(event.target.value);
display.textContent = angle;
setInclineAngle(angle);
});
</script>Observing the Results
With frictionStatic: 0.5, the block remains stationary
until the slider passes approximately \(26.5^\circ\) (\(\arctan(0.5)\)). Beyond this angle,
gravitational component down the slope exceeds the maximum static
frictional force, causing the block to slide down the ramp under the
influence of its kinetic friction coefficient (0.3).