How to Draw Matter.js Trajectory Prediction Dots

Predicting the trajectory of a launched body in Matter.js requires simulating its discrete numerical integration rather than relying on standard continuous kinematic formulas. This guide explains how Matter.js processes motion—specifically accounting for gravity and air friction—and provides a lightweight, step-by-step JavaScript implementation to calculate and render trajectory prediction dots on an HTML5 canvas.

Understanding Matter.js Physics Integration

Matter.js updates body positions using a discrete semi-implicit Euler integration method applied at fixed delta time steps (usually 60 Hz or approximately 16.66ms). Standard projectile motion equations (\(x = v_x t\) and \(y = v_y t + \frac{1}{2} g t^2\)) drift out of sync with Matter.js simulations because they ignore the engine's step-based air friction damping.

Each frame, Matter.js updates linear velocity and position roughly as follows:

  1. Gravity Application: The engine adds global gravity to the body’s velocity.
  2. Air Resistance Damping: The velocity is scaled down by (1 - body.frictionAir).
  3. Position Integration: The new velocity is added to the current position.

To draw perfectly matching prediction dots, your code must iterate through these identical calculations step-by-step.

The Trajectory Prediction Algorithm

Instead of instantiating real Matter.js bodies for simulation, calculate the virtual points using a lightweight projection loop:

function getTrajectoryPoints(startX, startY, initialVelocity, engine, options = {}) {
    const points = [];
    const steps = options.steps || 60; // Total dots to draw
    const stepInterval = options.stepInterval || 2; // Steps between dots for spacing
    
    // Engine parameters
    const gravity = engine.gravity;
    const gravityScale = gravity.scale; // Default: 0.001
    const gx = gravity.x * gravityScale;
    const gy = gravity.y * gravityScale;
    
    // Body parameters (defaults from standard Matter.js body)
    const frictionAir = options.frictionAir !== undefined ? options.frictionAir : 0.01;
    const damping = 1 - frictionAir;

    // Virtual state
    let posX = startX;
    let posY = startY;
    let vx = initialVelocity.x;
    let vy = initialVelocity.y;

    for (let i = 0; i < steps * stepInterval; i++) {
        // 1. Apply gravity
        vx += gx;
        vy += gy;

        // 2. Apply air friction
        vx *= damping;
        vy *= damping;

        // 3. Update position
        posX += vx;
        posY += vy;

        // Collect point based on the spacing interval
        if (i % stepInterval === 0) {
            points.push({ x: posX, y: posY });
        }
    }

    return points;
}

Rendering the Dots on Canvas

Once the coordinates are calculated, render them onto your canvas context before or after rendering the Matter.js world.

function drawTrajectory(context, points) {
    context.save();
    
    for (let i = 0; i < points.length; i++) {
        const point = points[i];
        
        // Optionally decrease size or opacity along the path
        const alpha = 1 - (i / points.length);
        const radius = Math.max(1, 4 * alpha);

        context.beginPath();
        context.arc(point.x, point.y, radius, 0, Math.PI * 2);
        context.fillStyle = `rgba(255, 255, 255, ${alpha})`;
        context.fill();
    }
    
    context.restore();
}

Implementing in the Animation Loop

Integrate the trajectory generator into your aiming logic, typically triggered during drag or aim events:

// Example usage inside an event or render loop
const initialVelocity = {
    x: (dragStartX - currentMouseX) * 0.1,
    y: (dragStartY - currentMouseY) * 0.1
};

const trajectoryPoints = getTrajectoryPoints(
    launchOrigin.x,
    launchOrigin.y,
    initialVelocity,
    engine,
    {
        frictionAir: 0.01,
        steps: 30,
        stepInterval: 3
    }
);

drawTrajectory(canvasContext, trajectoryPoints);

Matching Non-Default Settings

If your simulation modifies engine.timing.timeScale or uses variable delta times, adjust damping and gravity multipliers accordingly:

This ensures the trajectory points directly overlap the path the physical body traverses upon launch.