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:
- Create a lookup table (map) matching original bodies to their newly cloned counterparts.
- Clone all bodies and record the mappings.
- Clone all constraints, updating their body references using the lookup table.
- 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
- Offsets: Providing an offset during the clone operation ensures that the duplicate composite does not spawn directly on top of the original, avoiding explosive physics collisions on the first frame.
- Complex Geometry: If your bodies contain internal
parts (
body.parts), ensureBody.setVerticesor compound body cloning logic is tailored to preserve compound hierarchies. - External Constraints: If a constraint inside the
composite connects to a body outside the composite,
bodyMap.get(constraint.bodyB)will returnundefined. In such cases, determine whether the constraint should bind to the original external body or remain disconnected.