Modeling Thermal Plumes and Gliders in Matter.js
This article explains how to simulate atmospheric thermal air plumes and glider thermalling behavior inside the Matter.js 2D physics engine. By combining a mathematical force field representing a rising column of heated air with custom aerodynamic equations for glider lift, drag, and sink rates, you can realistically model how unpowered aircraft locate, enter, and circle within updrafts to gain altitude.
Core Architecture
Matter.js is a rigid-body physics engine that does not natively
calculate fluid dynamics or continuous aerodynamics. To model
thermalling, you must decouple the simulation into two custom force
layers executed inside the beforeUpdate engine event:
- Thermal Plume Field: A spatial region that imparts vertical velocity and buoyancy to the surrounding air mass.
- Glider Aerodynamics Engine: A custom physics resolution that computes lift and induced drag relative to the glider's airspeed and angle of attack.
1. Modeling the Thermal Air Plume
A thermal plume rises due to buoyancy and typically follows a bell-curve velocity distribution (Gaussian profile) across its horizontal cross-section, with the strongest lift at the core and sink (downdraft) along the exterior edges.
Define the thermal as an abstract spatial region rather than a rigid body:
const thermal = {
x: 400, // Center of thermal
yBottom: 800, // Ground release point
yTop: 50, // Inversion layer/cloud base
radius: 120, // Core radius
maxUpdraft: 0.005, // Maximum upward force
verticalSpeed: 1.5 // Rate at which the thermal bubble ascends
};During each engine tick, calculate the horizontal distance from the glider to the thermal core to determine the local upward force:
function applyThermalForces(glider, thermal) {
const dx = glider.position.x - thermal.x;
const dy = glider.position.y;
// Check if glider is within vertical and horizontal bounds
if (dy <= thermal.yBottom && dy >= thermal.yTop && Math.abs(dx) < thermal.radius * 1.5) {
// Gaussian distribution of vertical lift
const factor = Math.exp(-Math.pow(dx / thermal.radius, 2));
// Core produces upward lift; outer edges produce slight sink
const updraft = (factor > 0.1)
? -thermal.maxUpdraft * factor
: thermal.maxUpdraft * 0.1;
Matter.Body.applyForce(glider, glider.position, { x: 0, y: updraft });
}
}2. Modeling Glider Aerodynamics
A glider in Matter.js requires accurate aerodynamic forces (Lift and Drag) to convert forward kinetic energy into vertical support. Without this, applying a thermal force merely pushes a generic object upward without the dynamics of flying.
Set the glider’s native frictionAir to 0 to
handle drag manually using aerodynamic equations:
- Lift Force: Perpendicular to the relative wind.
- Drag Force: Parallel and opposite to the relative wind.
function applyGliderAerodynamics(glider) {
const velocity = glider.velocity;
const speedSq = velocity.x * velocity.x + velocity.y * velocity.y;
if (speedSq < 0.01) return; // Ignore at near-zero velocity
const speed = Math.sqrt(speedSq);
const velocityAngle = Math.atan2(velocity.y, velocity.x);
const chordAngle = glider.angle;
// Angle of Attack (alpha)
const alpha = chordAngle - velocityAngle;
// Aerodynamic coefficients (simplified linear range)
const cL = 2 * Math.PI * alpha; // Lift coefficient
const cD = 0.04 + (cL * cL) / (Math.PI * 8.0); // Parasitic + induced drag
// Calculate magnitudes: Force = 0.5 * rho * v^2 * Area * C
const airDensity = 0.001;
const wingArea = 20;
const liftMag = 0.5 * airDensity * speedSq * wingArea * cL;
const dragMag = 0.5 * airDensity * speedSq * wingArea * cD;
// Resolve vectors
const liftAngle = velocityAngle - Math.PI / 2;
const dragAngle = velocityAngle + Math.PI;
const totalForce = {
x: Math.cos(liftAngle) * liftMag + Math.cos(dragAngle) * dragMag,
y: Math.sin(liftAngle) * liftMag + Math.sin(dragAngle) * dragMag
};
Matter.Body.applyForce(glider, glider.position, totalForce);
}3. Implementing Thermalling and Circling
To simulate "thermalling" (circling within an updraft) in a 2D environment, the glider must continuously alter its attitude (pitch/bank) to stay inside the thermal's radius:
- Sink Rate vs. Updraft: In still air, the glider maintains a polar sink rate determined by its drag. To climb, the thermal updraft must exceed this sink rate.
- Turn Mechanics: In a 2D side-view simulation, cycling through turn states can be represented by varying the glider's effective wing area or periodically reversing its horizontal velocity while increasing drag to simulate bank-angle lift loss.
- Top-Down Implementation Alternative: In a top-down view (X/Y plane represents ground coordinates), thermalling is achieved by applying continuous torque to pitch the glider in a circle:
// Continuous turn input to core the thermal
const bankTorque = 0.0002;
Matter.Body.setAngularVelocity(glider, glider.angularVelocity + bankTorque);Integration Loop
Tie the system together using the Matter.js engine loop:
Matter.Events.on(engine, 'beforeUpdate', () => {
applyGliderAerodynamics(glider);
applyThermalForces(glider, thermal);
// Slowly drift thermal upward or downwind
thermal.x += 0.1; // Wind drift
thermal.yBottom -= thermal.verticalSpeed;
thermal.yTop -= thermal.verticalSpeed;
});This combination creates an equilibrium where the glider continuously trades altitude for airspeed, while the external energy injected by the thermal field offsets drag losses, allowing the aircraft to climb.