Applying Directional Wind Gusts in Matter.js

This guide explains how to simulate realistic, directional wind gusts with randomized noise parameters in Matter.js. Because Matter.js does not feature a native environmental force engine, you must calculate dynamic vector forces and apply them to target bodies during the physics update cycle. By leveraging the beforeUpdate event alongside procedural noise functions like Perlin noise or sinusoidal wave combinations, you can generate continuous, organic airflow with unpredictable bursts and turbulence.

1. Setting Up the Wind Loop

To affect bodies consistently, wind must be computed and applied before every physics step. Hook into the Matter.js engine update loop using the Events.on method with the beforeUpdate event.

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

const engine = Engine.create();

Events.on(engine, 'beforeUpdate', (event) => {
    const time = event.timestamp * 0.001; // Current simulation time in seconds
    applyWindToBodies(engine.world.bodies, time);
});

2. Generating Randomized Noise Parameters

True random values (Math.random()) create jittery, unnatural motion. For realistic wind gusts, use coherent noise—such as Simplex noise or multi-octave sine approximations—to vary wind speed and direction smoothly over time.

Below is an implementation using layered sine waves to approximate continuous 1D noise without external dependencies:

function getNoise(time, frequency, seed = 0) {
    return (
        Math.sin(time * frequency + seed) * 0.5 +
        Math.sin(time * frequency * 2.3 + seed * 1.5) * 0.3 +
        Math.sin(time * frequency * 5.1 + seed * 2.0) * 0.2
    );
}

3. Calculating the Directional Wind Vector

Combine a base directional vector with noise-driven offsets for gust intensity and directional variance.

const windConfig = {
    baseDirection: { x: 1, y: -0.1 }, // Generally blowing right, slightly upward
    baseStrength: 0.0005,
    gustFrequency: 0.8,
    gustScale: 0.002,
    turbulenceFrequency: 2.5,
    turbulenceScale: 0.3 // Radians of angle variation
};

function calculateWindForce(time) {
    // 1. Calculate gust magnitude (0 to 1)
    const gustNoise = Math.max(0, getNoise(time, windConfig.gustFrequency, 42));
    const totalStrength = windConfig.baseStrength + (gustNoise * windConfig.gustScale);

    // 2. Calculate directional turbulence (angle deviation)
    const angleOffset = getNoise(time, windConfig.turbulenceFrequency, 99) * windConfig.turbulenceScale;
    const baseAngle = Math.atan2(windConfig.baseDirection.y, windConfig.baseDirection.x);
    const finalAngle = baseAngle + angleOffset;

    // 3. Create final directional force vector
    return {
        x: Math.cos(finalAngle) * totalStrength,
        y: Math.sin(finalAngle) * totalStrength
    };
}

4. Applying the Force to Bodies

Iterate over the active bodies in the simulation, ensuring static bodies are ignored. Scale the force based on the surface area or mass of the body if you want larger objects to experience proportional drag.

function applyWindToBodies(bodies, time) {
    const windForce = calculateWindForce(time);

    for (let i = 0; i < bodies.length; i++) {
        const body = bodies[i];

        // Skip static bodies and sleeping bodies
        if (body.isStatic || body.isSleeping) continue;

        // Apply aerodynamic drag factor based on mass or surface area
        const dragFactor = body.mass; 
        const scaledForce = {
            x: windForce.x * dragFactor,
            y: windForce.y * dragFactor
        };

        // Apply force at the center of mass
        Body.applyForce(body, body.position, scaledForce);
    }
}

5. Adding Spatial Noise (Optional)

If your canvas is large, wind forces should vary by location as well as time. Incorporate the body's coordinates into the noise function:

function calculateSpatialWindForce(time, position) {
    const spatialSeed = position.x * 0.005 + position.y * 0.002;
    const localGust = Math.max(0, getNoise(time + spatialSeed, windConfig.gustFrequency, 42));
    const strength = windConfig.baseStrength + (localGust * windConfig.gustScale);

    return {
        x: windConfig.baseDirection.x * strength,
        y: windConfig.baseDirection.y * strength
    };
}

Passing body.position into this function before invoking Body.applyForce ensures that wind gusts roll across the screen across different positions rather than hitting all bodies simultaneously.