Simulating Sand Dune Migration in Matter.js

This guide explains how to simulate aerodynamic sand dune migration within the Matter.js 2D physics engine. By combining granular particle physics, realistic angle-of-repose friction, and customized wind vector forces mimicking saltation and creep, you can model emergent barchan and transverse dune behavior in a real-time browser environment.

Physical Principles of Dune Movement

Aeolian (wind-driven) sand transport relies on three primary mechanisms:

  1. Creep: Heavy grains rolling along the surface due to wind and particle impacts.
  2. Saltation: Grains lifted into the air by wind shear, traveling downwind, and impacting other grains upon landing.
  3. Avalanching: Grain accumulation exceeding the natural angle of repose (typically 30° to 34° for dry sand), causing structural collapse on the slip face.

Matter.js handles rigid-body collisions natively, making it well-suited for modeling granular pile mechanics and avalanches through circular rigid bodies. However, aerodynamic forces are not built into the engine and must be applied programmatically each simulation tick.

Configuring Granular Sand Bodies

To simulate cohesive yet granular behavior, configure individual sand particles as small circular bodies with high friction and minimal restitution (bounciness). High friction ensures that grains interlock to form stable slopes up to the angle of repose.

const sandRadius = 3;
const sandOptions = {
    friction: 0.8,
    frictionStatic: 0.9,
    restitution: 0.05,
    density: 0.002
};

function createSandGrain(x, y) {
    return Matter.Bodies.circle(x, y, sandRadius, sandOptions);
}

Modeling Aerodynamic Wind and Saltation

Wind force acts predominantly along the horizontal axis, but saltation requires an aerodynamic lift component caused by air turbulence and surface shear stress.

To apply wind, hook into the beforeUpdate event of the Matter.js engine:

const prevailingWindVector = { x: 0.00015, y: -0.00003 };

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

    bodies.forEach(body => {
        // Only affect sand particles, ignoring boundaries
        if (body.isStatic) return;

        // Apply aerodynamic drag based on position
        applyWindForces(body, prevailingWindVector);
    });
});

Surface Detection and Force Application

In nature, wind only acts upon the surface layer of sand. If you apply wind forces globally to all bodies, the entire dune will slide uniformly as a block instead of migrating organically.

You can restrict wind forces to surface grains using one of two methods:

Method 1: Grid-Based Height Map Check

Calculate the highest active particle in vertical vertical slices (columns) along the x-axis:

function applyWindForces(body, wind) {
    // Only apply forces to exposed grains near the surface
    const surfaceThresholdY = getSurfaceYAt(body.position.x);

    if (body.position.y <= surfaceThresholdY + sandRadius * 2) {
        // Add pseudo-random turbulence to simulate saltation lift
        const turbulence = (Math.random() - 0.5) * 0.00005;
        
        Matter.Body.applyForce(body, body.position, {
            x: wind.x * body.mass,
            y: (wind.y + turbulence) * body.mass
        });
    }
}

Method 2: Neighbor Density Checks

Count how many neighboring bodies surround a given grain using Matter.Query.point or spatial hashes. Bodies surrounded on all sides are subterranean and should receive zero wind force, while bodies with few neighbors above them receive full wind shear.

Emergent Dune Dynamics

Once tuned, the simulation produces realistic self-organizing dune morphologies:

  1. Windward Slope (Stoss): Wind pushes surface grains up the gentle windward slope via saltation and creep.
  2. Crest and Separation: As sand reaches the crest, it drops into the aerodynamic shelter (wake zone) on the leeward side where wind shear drops to zero.
  3. Leeward Slope (Slip Face): Sand accumulates at the crest until the slope angle exceeds the angle of repose. The inter-particle friction in Matter.js fails under gravity, triggering a localized avalanche down the slip face.
  4. Forward Migration: This continuous transfer of sand from the upwind side to the downwind slope causes the entire dune structure to migrate in the direction of the prevailing wind.

Optimization Strategies

Because granular simulations require hundreds or thousands of bodies, maintain 60 FPS performance with these optimizations: