Orbital Insertion Delta-V Maneuvers in Matter.js

This article explains how to calculate and execute orbital insertion delta-v maneuvers within the Matter.js 2D physics engine. You will learn the underlying orbital mechanics required to determine the necessary velocity change (\(\Delta v\)) for capture, how to determine burn direction, and how to translate those calculations into body impulses using the Matter.js API.


1. Setting Up the Gravitational Model

Matter.js defaults to uniform downward gravity. For orbital mechanics, disable the world's default gravity and apply a radial gravitational force toward a central body (such as a planet) on every engine update:

engine.gravity.scale = 0; // Disable default gravity

Apply Newton's law of universal gravitation inside an event listener:

\[\vec{F}_g = - \frac{G \cdot M \cdot m}{r^2} \hat{r}\]

In Matter.js code:

Matter.Events.on(engine, 'beforeUpdate', () => {
    const rVector = {
        x: planet.position.x - craft.position.x,
        y: planet.position.y - craft.position.y
    };
    const distance = Math.hypot(rVector.x, rVector.y);
    const forceMagnitude = (G * planet.mass * craft.mass) / (distance * distance);

    const force = {
        x: (rVector.x / distance) * forceMagnitude,
        y: (rVector.y / distance) * forceMagnitude
    };

    Matter.Body.applyForce(craft, craft.position, force);
});

Here, \(\mu = G \cdot M\) represents the standard gravitational parameter of the central attractor.


2. Calculating the Required Delta-V

An orbital insertion maneuver transitions a spacecraft from an unbound trajectory (hyperbolic) or an eccentric transfer orbit into a stable, closed orbit (typically circular or elliptical) around the target body.

The velocity \(v\) at any point in a Keplerian orbit is given by the Vis-viva equation:

\[v = \sqrt{\mu \left( \frac{2}{r} - \frac{1}{a} \right)}\]

Where:

Case: Circular Orbit Insertion at Periapsis

To capture into a circular orbit at the current radius \(r\), the target semi-major axis is \(a = r\). The required circular velocity \(v_{circ}\) is:

\[v_{circ} = \sqrt{\frac{\mu}{r}}\]

If your spacecraft arrives at periapsis with a scalar speed \(v_{current}\), the magnitude of the required delta-v is:

\[\Delta v = v_{circ} - v_{current}\]

Because \(v_{current} > v_{circ}\), \(\Delta v\) will be negative, meaning a retrograde (braking) burn is required.


3. Converting Delta-V to a Matter.js Impulse

An impulse (\(\vec{J}\)) represents an instantaneous change in linear momentum:

\[\vec{J} = m \cdot \Delta \vec{v}\]

Where:

Step-by-Step Implementation

  1. Calculate the current speed and unit vector:
const vx = craft.velocity.x;
const vy = craft.velocity.y;
const currentSpeed = Math.hypot(vx, vy);

// Tangential unit vector (prograde)
const progradeUnit = {
    x: vx / currentSpeed,
    y: vy / currentSpeed
};
  1. Compute the delta-v scalar:
const distance = Math.hypot(
    planet.position.x - craft.position.x,
    planet.position.y - craft.position.y
);
const mu = G * planet.mass;
const targetSpeed = Math.sqrt(mu / distance); // Circular insertion speed

const deltaV = targetSpeed - currentSpeed; // Negative value for retrograde
  1. Apply the impulse vector to the body:

In Matter.js, an impulse can be applied either by updating the velocity directly via Matter.Body.setVelocity or by applying a discrete force scaled to engine delta time. Directly modifying velocity is the most precise method for instantaneous impulses:

// Method A: Direct velocity adjustment (Exact Impulse)
Matter.Body.setVelocity(craft, {
    x: craft.velocity.x + progradeUnit.x * deltaV,
    y: craft.velocity.y + progradeUnit.y * deltaV
});

If you prefer using Matter.Body.applyForce, an impulse \(\vec{J}\) maps to a force \(\vec{F}\) applied over one simulation tick (\(\Delta t\)):

\[\vec{F} = \frac{\vec{J}}{\Delta t} = \frac{m \cdot \Delta \vec{v}}{\Delta t}\]

// Method B: Using applyForce over a single step
const deltaT = engine.timing.delta; // Engine step in milliseconds (default: 16.666ms)
const impulseX = craft.mass * (progradeUnit.x * deltaV);
const impulseY = craft.mass * (progradeUnit.y * deltaV);

Matter.Body.applyForce(craft, craft.position, {
    x: impulseX / deltaT,
    y: impulseY / deltaT
});

4. Non-Circular (Elliptical) Insertions

If the desired target is an elliptical orbit with a specified apoapsis radius (\(r_a\)) and periapsis radius (\(r_p\)), the target semi-major axis is:

\[a = \frac{r_a + r_p}{2}\]

Substituting \(a\) into the Vis-viva equation gives the necessary insertion speed at periapsis:

\[v_{target} = \sqrt{\mu \left( \frac{2}{r_p} - \frac{1}{a} \right)}\]

Then calculate \(\Delta v = v_{target} - v_{current}\) and execute the velocity update at periapsis using the same impulse method.