Simulate Flying Squirrel Gliding in Matter.js

This article explains how to simulate the aerodynamic gliding descent of a flying squirrel using the Matter.js 2D physics engine. You will learn how to calculate custom aerodynamic lift and drag vectors based on velocity and angle of attack, implement an adjustable membrane area (patagium) parameter, and apply these forces dynamically within the engine's update loop to produce realistic gliding trajectories.


Core Aerodynamic Concepts

Matter.js handles basic rigid-body dynamics and gravity, but it does not simulate aerodynamics by default. Gliding flight relies on two primary aerodynamic forces opposing gravity:

  1. Drag (\(F_D\)): Acts parallel and opposite to the direction of motion. \[F_D = \frac{1}{2} \rho v^2 C_D A\]
  2. Lift (\(F_L\)): Acts perpendicular to the direction of motion. \[F_L = \frac{1}{2} \rho v^2 C_L A\]

Where:


Implementing the Simulation

1. Define Physics and Squirrel State

Set up standard Matter.js modules and track the squirrel's flight properties, including the adjustable membrane area.

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

const engine = Engine.create();
const world = engine.world;

// Create the squirrel body
const squirrel = Bodies.rectangle(100, 100, 60, 20, {
    mass: 1.5,
    frictionAir: 0 // Disable default air friction to use custom equations
});

Composite.add(world, squirrel);

// Aerodynamic parameters
const flightState = {
    membraneArea: 1.0,  // Scale factor (e.g., 0.2 retracted, 1.0 fully deployed)
    airDensity: 0.0012, // Scaled for simulation canvas
    maxLiftCoeff: 1.2,
    baseDragCoeff: 0.1
};

2. Calculate Angle of Attack and Coefficients

The angle of attack is the difference between the squirrel body's pitch angle and its velocity vector.

function getAerodynamicCoefficients(angleOfAttack) {
    // Lift rises with angle of attack, peaks around 15-20 degrees (~0.3 rad), then stalls
    const cl = flightState.maxLiftCoeff * Math.sin(2 * angleOfAttack);
    
    // Drag increases as angle of attack departs from zero
    const cd = flightState.baseDragCoeff + (1 - Math.cos(angleOfAttack)) * 1.5;
    
    return { cl, cd };
}

3. Compute and Apply Aerodynamic Forces

Hook into Events.on(engine, 'beforeUpdate', ...) to compute and apply the forces at each time step.

Events.on(engine, 'beforeUpdate', () => {
    const velocity = squirrel.velocity;
    const speed = Vector.magnitude(velocity);

    // Skip calculations if stationary
    if (speed < 0.1) return;

    // Direction of motion
    const velocityAngle = Math.atan2(velocity.y, velocity.x);
    
    // Angle of Attack (body rotation relative to trajectory)
    const angleOfAttack = squirrel.angle - velocityAngle;

    const { cl, cd } = getAerodynamicCoefficients(angleOfAttack);

    // Dynamic pressure dynamic scaling: 0.5 * rho * v^2 * Area
    const dynamicPressure = 0.5 * flightState.airDensity * Math.pow(speed, 2) * flightState.membraneArea;

    const liftMagnitude = dynamicPressure * cl;
    const dragMagnitude = dynamicPressure * cd;

    // Drag vector: opposite to velocity vector
    const dragAngle = velocityAngle + Math.PI;
    const dragForce = {
        x: Math.cos(dragAngle) * dragMagnitude,
        y: Math.sin(dragAngle) * dragMagnitude
    };

    // Lift vector: perpendicular to velocity vector (upward relative to path)
    const liftAngle = velocityAngle - Math.PI / 2;
    const liftForce = {
        x: Math.cos(liftAngle) * liftMagnitude,
        y: Math.sin(liftAngle) * liftMagnitude
    };

    // Total aerodynamic force
    const totalForce = Vector.add(dragForce, liftForce);

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

Modulating Membrane Area in Real Time

To simulate a flying squirrel extending or retracting its patagium, adjust flightState.membraneArea:

// Example UI or Input bindings:
window.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowUp') {
        // Extend membrane
        flightState.membraneArea = Math.min(1.5, flightState.membraneArea + 0.1);
    } else if (e.key === 'ArrowDown') {
        // Retract membrane
        flightState.membraneArea = Math.max(0.1, flightState.membraneArea - 0.1);
    }
});

Controlling Pitch

Glide trajectory depends directly on body rotation. Apply small amounts of torque using Body.setAngularVelocity(squirrel, value) or direct rotation updates to simulate the squirrel using its tail and arms to pitch up or down, allowing transitions between high-speed dives and flared landings.