How to Simulate Quadratic Drag in Matter.js

This article explains how to implement realistic atmospheric drag that scales quadratically with velocity in Matter.js. While Matter.js includes a built-in frictionAir property, it simulates linear damping where resistance is directly proportional to speed. By disabling this default behavior and applying custom aerodynamic forces during the physics update loop, you can accurately model real-world fluid dynamics where high-speed objects experience significantly higher resistance.

The Physics of Quadratic Drag

In fluid dynamics, drag force (\(F_d\)) is calculated using the drag equation:

\[F_d = \frac{1}{2} \rho v^2 C_d A\]

Where:

For a 2D physics simulation, constants such as \(\frac{1}{2}\), \(\rho\), \(C_d\), and \(A\) can be combined into a single coefficient \(k\).

Because force is a vector that directly opposes the direction of movement, the force vector is computed as:

\[\vec{F}_d = -k \cdot |\vec{v}| \cdot \vec{v}\]

This formulation guarantees that the magnitude of the force scales with \(v^2\) while remaining pointed directly opposite to the velocity vector.

Step-by-Step Implementation

1. Disable Default Linear Drag

When creating a body, set frictionAir to 0. If left at its default value (0.01), Matter.js will apply its built-in linear damping on top of your quadratic calculations.

const body = Matter.Bodies.circle(x, y, radius, {
    frictionAir: 0
});
Matter.Composite.add(engine.world, body);

2. Hook into the Engine's Update Loop

Use Matter.Events.on to attach a listener to the beforeUpdate event. This hook executes before the engine integrates velocities and resolves collisions for the next frame, making it the ideal place to apply external forces.

3. Calculate and Apply the Force Vector

Within the loop:

  1. Extract the current velocity vector (\(v_x, v_y\)).
  2. Calculate the speed (magnitude).
  3. Compute the drag force components using the combined drag factor \(k\).
  4. Apply the resulting force at the body's center of mass using Matter.Body.applyForce().

Complete Code Example

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

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

// Create a body with linear drag disabled
const projectile = Bodies.circle(100, 300, 20, {
    frictionAir: 0,
    restitution: 0.8
});

Composite.add(world, projectile);

// Define the quadratic drag coefficient
// Adjust this value based on your simulation's scale
const DRAG_COEFFICIENT = 0.001;

// Apply quadratic drag before every physics step
Events.on(engine, 'beforeUpdate', () => {
    const velocity = projectile.velocity;
    const speed = Vector.magnitude(velocity);

    // Prevent unnecessary calculations when the body is nearly stationary
    if (speed < 0.001) return;

    // F_drag = -k * |v| * v
    // Multiplying speed by velocity components yields a v^2 magnitude
    const dragMagnitudeFactor = DRAG_COEFFICIENT * speed;
    const dragForce = {
        x: -dragMagnitudeFactor * velocity.x,
        y: -dragMagnitudeFactor * velocity.y
    };

    Body.applyForce(projectile, projectile.position, dragForce);
});

Considerations for Stability