Model Space Station Docking in Matter.js

This article explains how to build a reliable space station docking clamp mechanism featuring mechanical alignment guides using the Matter.js 2D physics engine. You will learn how to shape angled guide funnels with low-friction compound bodies, detect alignment with sensor triggers, and secure incoming spacecraft using dynamic physical constraints that simulate mechanical locking latches.

1. Designing the Funnel Alignment Guides

Mechanical docking relies on passive alignment guides—typically a conical probe on the moving craft and an angled receptacle (drogue) on the station. In a 2D engine like Matter.js, this is achieved using angled rectangular bodies assembled as a V-shaped funnel.

Set the friction on these surfaces very low to prevent ships from catching or rotating violently on impact:

const guideOptions = {
    isStatic: true,
    friction: 0.01,
    frictionStatic: 0,
    restitution: 0.05,
    collisionFilter: {
        category: 0x0002,
        mask: 0x0001
    }
};

// V-shaped guide funnel walls
const leftGuide = Matter.Bodies.rectangle(380, 280, 80, 10, {
    ...guideOptions,
    angle: Math.PI / 4 // 45-degree angle
});

const rightGuide = Matter.Bodies.rectangle(420, 280, 80, 10, {
    ...guideOptions,
    angle: -Math.PI / 4 // -45-degree angle
});

2. Crafting the Docking Probe

The incoming ship requires a narrow probe head matching the inverse shape of the funnel guide. Using a compound body, combine the ship's main hull with a forward-extending probe tip:

const shipBody = Matter.Bodies.rectangle(400, 100, 60, 60, { density: 0.005 });
const probeTip = Matter.Bodies.circle(400, 140, 8, {
    friction: 0.01,
    restitution: 0.05
});

const spacecraft = Matter.Body.create({
    parts: [shipBody, probeTip],
    collisionFilter: {
        category: 0x0001,
        mask: 0x0002
    }
});

3. Setting Up the Latch Sensor

Rather than attempting to clamp purely through rigid geometry, use an invisible sensor inside the base of the funnel to detect when the probe tip is fully seated:

const latchSensor = Matter.Bodies.rectangle(400, 310, 20, 10, {
    isStatic: true,
    isSensor: true,
    render: { visible: false }
});

4. Engaging the Locking Clamps

When the probe contacts the sensor, instantiate dynamic constraints representing the physical clamps. To prevent physics instability from abrupt velocity changes, start with a slightly lower constraint stiffness or apply dampening before locking completely:

let isDocked = false;

Matter.Events.on(engine, 'collisionStart', (event) => {
    event.pairs.forEach((pair) => {
        const bodies = [pair.bodyA, pair.bodyB];
        
        if (bodies.includes(latchSensor) && bodies.includes(probeTip) && !isDocked) {
            isDocked = true;
            
            // Soft dampening clamp to absorb residual inertia
            const primaryClamp = Matter.Constraint.create({
                bodyA: probeTip,
                bodyB: latchSensor,
                pointA: { x: 0, y: 0 },
                pointB: { x: 0, y: 0 },
                stiffness: 0.2,
                damping: 0.1,
                length: 0
            });

            // Rotational stabilization clamp
            const alignmentClamp = Matter.Constraint.create({
                bodyA: shipBody,
                bodyB: latchSensor,
                pointA: { x: 0, y: -20 },
                pointB: { x: 0, y: -60 },
                stiffness: 0.1,
                damping: 0.05,
                length: 40
            });

            Matter.Composite.add(world, [primaryClamp, alignmentClamp]);

            // Lock to full rigidity after momentum dissipates
            setTimeout(() => {
                primaryClamp.stiffness = 1.0;
                alignmentClamp.stiffness = 1.0;
            }, 300);
        }
    });
});

5. Managing Undocking

To release the ship, remove the generated constraints from the world and re-enable collision logic after a short delay to ensure the ship clears the funnel without re-triggering the sensor:

function undock(primaryClamp, alignmentClamp) {
    Matter.Composite.remove(world, [primaryClamp, alignmentClamp]);
    
    // Apply a mild separation impulse
    Matter.Body.applyForce(spacecraft, spacecraft.position, { x: 0, y: -0.05 });

    setTimeout(() => {
        isDocked = false;
    }, 1000);
}