Simulating Hailstone Updrafts in Matter.js

This article explains how to model the suspension of hailstones within severe thunderstorm updrafts using the Matter.js 2D physics engine. By counteracting the default gravitational field with custom vertical aerodynamic forces, you can recreate the terminal velocity equilibrium that allows hailstones to grow inside a storm's core. You will learn how to configure the engine, calculate and apply continuous drag and lift forces using engine events, and introduce turbulent vertical velocity gradients.

Understanding the Physics of Hail Suspension

A hailstone remains suspended in an atmospheric updraft when the upward drag force exerted by rising air equals the downward force of gravity acting on the stone's mass (\(F_d = F_g\)). In fluid dynamics, this drag force depends on the air density, the projected frontal area of the hailstone, a drag coefficient, and the square of the relative wind speed.

In Matter.js, gravity applies a constant downward acceleration on all dynamic bodies. To simulate suspension, you must continuously apply an upward force to the body via Matter.Body.applyForce() within the engine update loop.

Initializing the Matter.js Environment

Begin by setting up a basic Matter.js scene containing the physics engine, a runner, and a render view with standard downward gravity.

const { Engine, Render, Runner, Bodies, Composite, Body, Vector, Events } = Matter;

const engine = Engine.create();
engine.gravity.y = 1; // Standard downward gravity

const render = Render.create({
    element: document.body,
    engine: engine,
    options: {
        width: 800,
        height: 600,
        wireframes: false
    }
});

Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);

Creating the Hailstone Body

Create hailstones as circular rigid bodies with custom properties to store physical parameters such as mass, cross-sectional area, and aerodynamic drag coefficients.

function createHailstone(x, y, radius) {
    const hailstone = Bodies.circle(x, y, radius, {
        density: 0.0009, // Approximate ice density (g/cm^3 mapped to Matter.js units)
        frictionAir: 0.005, // Linear damping
        restitution: 0.1,  // Low elasticity
        render: {
            fillStyle: '#E0F7FA'
        }
    });

    // Custom aerodynamic properties
    hailstone.aerodynamics = {
        dragCoefficient: 0.45, // Typical sphere drag coefficient
        area: Math.PI * Math.pow(radius, 2),
    };

    Composite.add(engine.world, hailstone);
    return hailstone;
}

const hailstone = createHailstone(400, 300, 20);

Applying the Updraft Force

To simulate the updraft, hook into the beforeUpdate event. Calculate the net relative velocity between the hailstone and the upward moving air column, compute the resulting aerodynamic drag, and apply it to the body.

// Updraft parameters (pointing upward in canvas coordinates)
const UPDRAFT_VELOCITY = -15; // Negative Y is upward in 2D canvas
const AIR_DENSITY = 0.0012;

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

    bodies.forEach(body => {
        if (!body.aerodynamics) return;

        // Relative velocity between updraft and the hailstone
        const relativeVelocityY = UPDRAFT_VELOCITY - body.velocity.y;

        // Drag formula: F = 0.5 * rho * v^2 * Cd * A
        const dragMagnitude = 0.5 * AIR_DENSITY * 
                              Math.pow(relativeVelocityY, 2) * 
                              body.aerodynamics.dragCoefficient * 
                              body.aerodynamics.area;

        // Ensure force direction matches relative air movement
        const forceDirection = Math.sign(relativeVelocityY);
        const forceY = forceDirection * dragMagnitude;

        // Apply force at the center of mass
        Body.applyForce(body, body.position, { x: 0, y: forceY });
    });
});

Modeling Updraft Core Turbulence

Thunderstorm updrafts are not uniform; they exhibit a bell-curve spatial velocity profile and temporal turbulence. You can model this by scaling the updraft strength based on the hailstone's horizontal position relative to the updraft core and adding high-frequency noise.

function getTurbulentUpdraft(positionX, timestamp) {
    const coreCenterX = 400;
    const coreWidth = 200;
    const peakUpdraft = -22;

    // Gaussian-like horizontal profile
    const distanceFromCenter = Math.abs(positionX - coreCenterX);
    const spatialFactor = Math.exp(-Math.pow(distanceFromCenter / (coreWidth / 2), 2));

    // High-frequency turbulence noise
    const turbulence = Math.sin(timestamp * 0.005) * 2.5 + (Math.random() - 0.5) * 1.5;

    return (peakUpdraft + turbulence) * spatialFactor;
}

Replace the static UPDRAFT_VELOCITY inside the beforeUpdate callback with getTurbulentUpdraft(body.position.x, engine.timing.timestamp) to achieve realistic suspension, oscillation, and eventual fallout when the hailstone drifts outside the updraft core.