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:
- \(\rho\) is the fluid density.
- \(v\) is the speed of the body relative to the fluid.
- \(C_d\) is the drag coefficient (dependent on shape).
- \(A\) is the cross-sectional reference area.
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:
- Extract the current velocity vector (\(v_x, v_y\)).
- Calculate the speed (magnitude).
- Compute the drag force components using the combined drag factor \(k\).
- 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
Coefficient Sizing: The value of
DRAG_COEFFICIENTmust be tuned carefully. Setting it too high can cause the applied drag force to exceed the body's current momentum in a single frame, resulting in jitter or sudden velocity reversals.Terminal Velocity: Because quadratic drag increases rapidly with speed, falling bodies will naturally approach an equilibrium state where gravitational force equals drag force:
\[v_{\text{terminal}} = \sqrt{\frac{m \cdot g}{k}}\]
Multiple Bodies: If you have multiple bodies requiring quadratic drag, iterate over an array of bodies or filter through
Composite.allBodies(world)inside thebeforeUpdatecallback, checking for custom tags or properties to apply the drag forces individually.