How to Simulate Air Resistance in Matter.js

Simulating air resistance in Matter.js can be achieved either by utilizing the engine's built-in linear drag property or by calculating and applying realistic quadratic aerodynamic forces manually. While the built-in frictionAir property provides an easy and lightweight way to slow bodies down over time, writing a custom drag function via the engine's update events allows for accurate physical simulations where drag scales with the square of velocity.

Method 1: Using Built-In frictionAir

Matter.js bodies have a native property called frictionAir. By default, this value is set to 0.01. Increasing this value will make an object slow down faster in both linear and angular motion, simulating resistance against an atmosphere.

You can set this property when creating the body:

const body = Matter.Bodies.circle(x, y, radius, {
    frictionAir: 0.05 // Higher value creates stronger resistance
});

You can also update it dynamically at runtime:

body.frictionAir = 0.08;

Limitation: The built-in frictionAir applies a linear deceleration proportional directly to the velocity (\(F \propto v\)). In real-world physics, air resistance is quadratic (\(F \propto v^2\)), meaning high-speed objects experience drastically more resistance than low-speed objects.


Method 2: Applying Realistic Quadratic Drag

To simulate realistic aerodynamic drag, set frictionAir: 0 on the body and apply a counter-force proportional to the square of its speed on each physics step using the beforeUpdate event.

The formula for aerodynamic drag magnitude is:

\[F_d = \frac{1}{2} \rho v^2 C_d A\]

In game development, this is often simplified into a single drag coefficient \(k\):

\[\vec{F}_d = -k \cdot |\vec{v}| \cdot \vec{v}\]

Here is how to implement this in Matter.js:

const { Engine, Events, Body, Vector } = Matter;

// 1. Create a body with no native air friction
const projectile = Matter.Bodies.circle(100, 300, 20, {
    frictionAir: 0
});
Matter.Composite.add(world, projectile);

// 2. Define your drag coefficient
const dragCoefficient = 0.005;

// 3. Apply custom drag force before each engine update
Events.on(engine, 'beforeUpdate', () => {
    const velocity = projectile.velocity;
    const speed = Vector.magnitude(velocity);

    if (speed > 0) {
        // Calculate drag force magnitude: F = k * v^2
        const dragMagnitude = dragCoefficient * speed * speed;

        // Calculate unit vector in the opposite direction of motion
        const dragDirection = Vector.negate(Vector.normalise(velocity));

        // Scale the direction by magnitude
        const dragForce = Vector.mult(dragDirection, dragMagnitude);

        // Apply force to the center of the body
        Body.applyForce(projectile, projectile.position, dragForce);
    }
});

Method 3: Global Air Resistance for Multiple Bodies

If you want air resistance to affect all dynamic bodies in the simulation, iterate over the composite's bodies inside the beforeUpdate listener:

Events.on(engine, 'beforeUpdate', () => {
    const bodies = Matter.Composite.allBodies(engine.world);
    const globalDrag = 0.002;

    bodies.forEach(body => {
        if (body.isStatic || body.isSleeping) return;

        const speed = Vector.magnitude(body.velocity);
        if (speed > 0) {
            const dragMagnitude = globalDrag * speed * speed;
            const dragForce = Vector.mult(
                Vector.negate(Vector.normalise(body.velocity)), 
                dragMagnitude
            );

            Body.applyForce(body, body.position, dragForce);
        }
    });
});

Choosing the Right Approach