Create a Vortex Force Effect in Matter.js
Modeling vortex airflow in Matter.js requires continuously applying custom forces to bodies during each physics step. This guide covers how to combine an inward radial force (attracting objects to a center) with a perpendicular tangential force (inducing circular motion), alongside applying torque to spin bodies on their own axes.
The Physics of a 2D Vortex
Matter.js does not have a native vortex field, so you must manipulate
body velocities manually inside the beforeUpdate engine
event. A convincing vortex requires three distinct components:
- Radial Force (Inward Pull): A vector directed from the body straight to the vortex center, mimicking suction or low air pressure.
- Tangential Force (Orbit): A vector perpendicular to the radial vector, pushing the body in an orbit around the center.
- Torque (Self-Spin): Direct rotational torque applied to the body to make it rotate independently while it orbits.
Mathematical Implementation
To calculate these forces for any body within the vortex's radius of effect:
- Find the offset vector between the vortex center \((x_c, y_c)\) and the body \((x_b, y_b)\): \[\Delta x = x_c - x_b\] \[\Delta y = y_c - y_b\]
- Calculate the distance: \(d = \sqrt{\Delta x^2 + \Delta y^2}\).
- Normalize the vector to get the radial direction: \[u_x = \frac{\Delta x}{d}, \quad u_y = \frac{\Delta y}{d}\]
- Derive the perpendicular tangential vector (swirl direction): \[t_x = -u_y, \quad t_y = u_x\] (Reversing the signs switches between clockwise and counter-clockwise flow).
Code Implementation
Attach a listener to the engine’s beforeUpdate event
using Matter.Events:
const { Engine, Render, Runner, Bodies, Composite, Events, Body, Vector } = Matter;
const vortexCenter = { x: 400, y: 300 };
const vortexRadius = 350;
const pullStrength = 0.0005; // Inward radial pull scale
const swirlStrength = 0.001; // Tangential swirl scale
const spinTorque = 0.05; // Self-rotation strength
Events.on(engine, 'beforeUpdate', () => {
const bodies = Composite.allBodies(engine.world);
for (let i = 0; i < bodies.length; i++) {
const body = bodies[i];
if (body.isStatic) continue;
const dx = vortexCenter.x - body.position.x;
const dy = vortexCenter.y - body.position.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Only apply forces within the vortex influence area
if (distance > 0 && distance < vortexRadius) {
// Normalize radial vector
const normalRadial = { x: dx / distance, y: dy / distance };
// Generate perpendicular tangential vector (clockwise)
const normalTangential = { x: -normalRadial.y, y: normalRadial.x };
// Optional: Attenuation factor (forces get stronger toward center)
const factor = 1 - (distance / vortexRadius);
// Calculate combined force vector
const forceX = (normalRadial.x * pullStrength + normalTangential.x * swirlStrength) * factor;
const forceY = (normalRadial.y * pullStrength + normalTangential.y * swirlStrength) * factor;
// Apply the total force to the center of the body
Body.applyForce(body, body.position, { x: forceX, y: forceY });
// Apply rotational spin around the body's center of mass
body.torque = spinTorque * factor;
}
}
});Tuning and Stability
- Dead Zone / Core Radius: As \(d \to 0\), normalizing the vector can cause extreme physics instability due to division by zero or massive acceleration spikes. Introduce a minimum distance threshold (e.g., if \(d < 20\), reduce or nullify the forces).
- Air Resistance (Friction Air): Matter.js bodies
will continuously gain kinetic energy unless dampening is applied.
Increase
body.frictionAir(e.g.,0.02to0.05) to represent the viscosity of the swirling air and maintain a steady orbital speed. - Falloff Function: In real fluid dynamics, inward
forces scale non-linearly. You can replace linear decay
(
1 - distance / vortexRadius) with inverse-square falloff (\(1 / d^2\)) or smoothstep curves to alter the tightness of the vortex spiral.