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.