Export Ammo.js Collision Shapes to JSON

This article explains how to export Ammo.js collision shapes to a standard JSON format. While Ammo.js does not feature a native, single-method JSON exporter, developers can successfully serialize collision shapes by querying shape properties programmatically and structuring them into custom JSON objects. Below, we explore the limitations of Ammo.js’s built-in serialization and provide a step-by-step guide on how to manually extract and format this data.

The Limitation of Native Ammo.js Serialization

Ammo.js is a WebAssembly/JavaScript port of the C++ Bullet Physics library. Bullet possesses a native serialization system (btDefaultSerializer) that exports physics worlds into a proprietary binary format (usually saved with a .bullet extension). However, this binary output is not human-readable, nor is it natively compatible with standard web workflows that rely heavily on JSON.

To get a clean, standard JSON representation of a collision shape (such as a box, sphere, cylinder, or triangle mesh), you must write a helper function that inspects the Ammo.js shape, extracts its geometric dimensions, and formats it.

How to Manually Export Shapes to JSON

To export Ammo.js shapes, you must identify the shape type using its broadphase entity type ID, retrieve its specific properties (like radius, half-extents, or vertex arrays), and map them to a JavaScript object which can then be converted to JSON using JSON.stringify().

Step 1: Identify the Shape Type

Ammo.js shapes inherit from btCollisionShape. You can determine the type of shape by calling the getShapeType() method. This returns an integer representing the shape’s type (e.g., BOX_SHAPE_PROXYTYPE or SPHERE_SHAPE_PROXYTYPE).

Step 2: Extract Specific Dimensions

Once the shape type is identified, cast or treat the shape object according to its specific subclass to retrieve its dimensions:

Step 3: Map to a JSON Structure

Create a helper function to compile these properties into a structured JavaScript object. Here is a practical example of how this serialization logic looks in JavaScript:

function serializeCollisionShape(shape) {
    const shapeType = shape.getShapeType();
    const exportData = {
        type: null,
        params: {}
    };

    // Ammo.js shape type constants (represent numerical IDs in Bullet)
    const BOX_SHAPE_PROXYTYPE = 0;
    const SPHERE_SHAPE_PROXYTYPE = 8;
    const CAPSULE_SHAPE_PROXYTYPE = 10;

    if (shapeType === BOX_SHAPE_PROXYTYPE) {
        exportData.type = "Box";
        const halfExtents = shape.getHalfExtentsWithMargin();
        exportData.params.halfExtents = {
            x: halfExtents.x(),
            y: halfExtents.y(),
            z: halfExtents.z()
        };
    } else if (shapeType === SPHERE_SHAPE_PROXYTYPE) {
        exportData.type = "Sphere";
        exportData.params.radius = shape.getRadius();
    } else if (shapeType === CAPSULE_SHAPE_PROXYTYPE) {
        exportData.type = "Capsule";
        exportData.params.radius = shape.getRadius();
        exportData.params.height = shape.getHalfHeight() * 2;
    } else {
        exportData.type = "Unsupported/Complex";
        exportData.params.shapeTypeId = shapeType;
    }

    return JSON.stringify(exportData, null, 2);
}

Handling Complex Mesh Shapes

For complex geometries like btBvhTriangleMeshShape or btConvexHullShape, exporting becomes more resource-intensive.

To export these shapes to JSON, you must access the underlying vertex and index buffers. For a convex hull, you can iterate through the points using the shape’s internal points array and store them as a flat array of coordinate triplets [x, y, z] in your JSON payload. Keep in mind that serializing large meshes into JSON can result in very large file sizes, so it is recommended to keep mesh resolutions as low as possible for physics collisions.