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:

  1. Radial Force (Inward Pull): A vector directed from the body straight to the vortex center, mimicking suction or low air pressure.
  2. Tangential Force (Orbit): A vector perpendicular to the radial vector, pushing the body in an orbit around the center.
  3. 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:

  1. 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\]
  2. Calculate the distance: \(d = \sqrt{\Delta x^2 + \Delta y^2}\).
  3. Normalize the vector to get the radial direction: \[u_x = \frac{\Delta x}{d}, \quad u_y = \frac{\Delta y}{d}\]
  4. 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