How to Clone a Composite in Matter.js

Cloning an entire Composite structure in Matter.js requires systematically duplicating its bodies, constraints, and nested composites while remapping references so new constraints bind to the cloned bodies rather than the originals. Because Matter.js does not feature a built-in deep-clone method for composites, developers must traverse the hierarchy manually. This guide provides a straightforward, reusable JavaScript function to deep-clone any composite structure cleanly and reliably.


The Challenge with Cloning Composites

A Composite in Matter.js contains arrays of bodies, constraints, and child composites. A shallow copy fails because constraints hold direct references to specific body instances (constraint.bodyA and constraint.bodyB). If you duplicate the composite without remapping, the new constraints will continue to pull and push the original bodies.

To properly clone a composite:

  1. Create a lookup table (map) matching original bodies to their newly cloned counterparts.
  2. Clone all bodies and record the mappings.
  3. Clone all constraints, updating their body references using the lookup table.
  4. Recursively process any child composites.

The Composite Cloning Function

Below is a complete implementation using Matter.js modules (Composite, Body, and Constraint):

import Matter from 'matter-js';

const { Composite, Body, Constraint, Vector } = Matter;

/**
 * Deep clones a Matter.js Composite, re-linking constraints to new bodies.
 * @param {Matter.Composite} composite - The composite to clone.
 * @param {Object} [options] - Optional overrides for position offset.
 * @param {Matter.Vector} [options.offset] - Translation offset for the clone.
 * @returns {Matter.Composite} A cloned Composite instance.
 */
function cloneComposite(composite, options = {}) {
    const bodyMap = new Map();
    const offset = options.offset || Vector.create(0, 0);

    function cloneBody(body) {
        // Deep copy vertices and basic physical properties
        const vertices = body.vertices.map(v => ({ x: v.x + offset.x, y: v.y + offset.y }));

        const clonedBody = Body.create({
            position: Vector.add(body.position, offset),
            angle: body.angle,
            isStatic: body.isStatic,
            isSensor: body.isSensor,
            density: body.density,
            friction: body.friction,
            frictionAir: body.frictionAir,
            frictionStatic: body.frictionStatic,
            restitution: body.restitution,
            collisionFilter: { ...body.collisionFilter },
            render: { ...body.render },
            label: body.label
        });

        // Set cloned geometry
        Body.setVertices(clonedBody, vertices);

        bodyMap.set(body, clonedBody);
        return clonedBody;
    }

    function cloneConstraint(constraint) {
        return Constraint.create({
            bodyA: constraint.bodyA ? bodyMap.get(constraint.bodyA) : null,
            bodyB: constraint.bodyB ? bodyMap.get(constraint.bodyB) : null,
            pointA: Vector.clone(constraint.pointA),
            pointB: Vector.clone(constraint.pointB),
            length: constraint.length,
            stiffness: constraint.stiffness,
            damping: constraint.damping,
            angularStiffness: constraint.angularStiffness,
            render: { ...constraint.render },
            label: constraint.label
        });
    }

    function traverseAndClone(sourceComposite) {
        const newComposite = Composite.create({
            label: sourceComposite.label
        });

        // 1. Clone all bodies at this level
        const clonedBodies = sourceComposite.bodies.map(cloneBody);
        Composite.add(newComposite, clonedBodies);

        // 2. Clone nested composites recursively
        for (const child of sourceComposite.composites) {
            const clonedChild = traverseAndClone(child);
            Composite.add(newComposite, clonedChild);
        }

        // 3. Clone constraints after bodies are mapped
        const clonedConstraints = sourceComposite.constraints.map(cloneConstraint);
        Composite.add(newComposite, clonedConstraints);

        return newComposite;
    }

    return traverseAndClone(composite);
}

How to Use the Cloning Utility

To duplicate an existing structure—such as a ragdoll, a bridge, or a vehicle—and place the copy at a different location in the world:

// Assume 'vehicleComposite' already exists in your engine's world
const vehicleClone = cloneComposite(vehicleComposite, {
    offset: Matter.Vector.create(200, 0) // Shift the clone 200px to the right
});

// Add the cloned composite to the world
Matter.Composite.add(engine.world, vehicleClone);

Key Considerations