How to clone or serialize a Planck.js world state?

Planck.js does not feature built-in, native methods for deep cloning or serializing a world state out of the box. Because the underlying physics engine relies heavily on complex, interconnected object graphs and circular references—such as doubly linked lists linking bodies, fixtures, joints, and contacts—standard JavaScript serialization methods like JSON.stringify() or structuredClone() will instantly fail. To successfully save, network, or replicate a planck.World instance, developers must manually extract the raw definition states of active physical components and reconstruct them into a fresh world instance.

Why Standard JavaScript Cloning Fails

Planck.js is a direct JavaScript/TypeScript rewrite of the C++ Box2D engine. It inherits an architecture that prioritizes performance and memory management over modern JavaScript data immutability.

The Strategy for Custom Serialization

To successfully export a snapshot of a Planck.js world, you must execute a structural extraction loop. This process isolates the essential physical properties needed to recreate the objects from scratch.

A standard serialization script iterates through the components of the world using the engine’s built-in linked list traversal getters:

const serializedData = { bodies: [], joints: [] };

// Traversing the body linked list
for (let body = world.getBodyList(); body; body = body.getNext()) {
    const bodyState = {
        type: body.getType(),
        position: body.getPosition().clone(),
        angle: body.getAngle(),
        linearVelocity: body.getLinearVelocity().clone(),
        angularVelocity: body.getAngularVelocity(),
        fixtures: []
    };

    // Traversing fixtures within the current body
    for (let fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) {
        const shape = fixture.getShape();
        bodyState.fixtures.push({
            type: shape.getType(),
            radius: shape.m_radius, // Extract specific shape details depending on type
            density: fixture.getDensity(),
            friction: fixture.getFriction(),
            restitution: fixture.getRestitution()
        });
    }

    serializedData.bodies.push(bodyState);
}

This structural object contains only primitive values, arrays, and flat structures, rendering it entirely safe for JSON.stringify() serialization or distribution across a network protocol.

Rehydrating and Deep Cloning the World State

Whether your goal is to recreate a deep copy of the world state for determinism checks, rollback networking, or a save-state feature, the rehydration process requires manual setup. You cannot inject raw state directly back into a living engine instance; you must generate a clean planck.World and reconstruct the elements sequentially.

  1. Initialize a New World Instance: Create a new world using the original gravity configuration and sleeping settings.
  2. Recreate Bodies and Fixtures: Run a loop over your serialized JSON data array. For each item, invoke world.createBody(bodyDef) using the saved position, type, and angle properties.
  3. Reapply Dynamic Velocities: After a body is instantiated, explicitly apply its linear and angular velocities via body.setLinearVelocity() and body.setAngularVelocity().
  4. Reconstruct Fixtures and Shapes: Iterate through the shape definitions stored for that body, instantiate the correct geometry subclass (e.g., planck.Box, planck.Circle), and append it using body.createFixture(shape, density).
  5. Re-link Joint Hierarchies: If your simulation includes joints, save the identifiers or indices of connected bodies during serialization, locate those specific bodies in the new world, and pass them into the appropriate joint definition factory.