Simulate a Volumetric Soft Body in Ammo.js

This article provides a step-by-step guide on how to simulate a volumetric soft body, such as a bouncing jelly sphere, using the Ammo.js physics engine. You will learn how to configure a soft body physics world, generate an ellipsoid soft body, tweak physical properties like pressure and stiffness to achieve a realistic jelly-like behavior, and map the physics simulation to a Three.js visual mesh.

1. Set Up the Soft Body Physics World

Unlike a standard rigid body simulation, a soft body simulation requires a specialized dynamics world. You must initialize btSoftRigidDynamicsWorld and provide a soft body solver.

// Initialize Bullet/Ammo physics
const collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
const softBodySolver = new Ammo.btDefaultSoftBodySolver();

// Create the soft/rigid dynamics world
const physicsWorld = new Ammo.btSoftRigidDynamicsWorld(
    dispatcher, 
    broadphase, 
    solver, 
    collisionConfiguration, 
    softBodySolver
);

// Set gravity
physicsWorld.setGravity(new Ammo.btVector3(0, -9.8, 0));

2. Create the Soft Body Sphere

To create a sphere, you can use Ammo’s btSoftBodyHelpers. The CreateEllipsoid helper generates a soft body mesh shaped like a sphere based on a center point, radius vectors, and a vertex count resolution.

const softBodyHelpers = new Ammo.btSoftBodyHelpers();

const center = new Ammo.btVector3(0, 5, 0); // Spawning height
const radius = new Ammo.btVector3(1, 1, 1); // 1-meter radius sphere
const numVertices = 128;                    // Resolution of the geometry

const softBody = softBodyHelpers.CreateEllipsoid(
    physicsWorld.getWorldInfo(), 
    center, 
    radius, 
    numVertices
);

// Add the soft body to the physics world
physicsWorld.addSoftBody(softBody, 1, -1);

3. Configure Jelly-Like Physical Properties

To make the soft body behave like a volumetric bouncy sphere (jelly) rather than a flat piece of cloth, you must adjust its configuration parameters. Specifically, you need to enable pressure, adjust stiffness, and configure collisions.

const config = softBody.get_m_cfg();

// Set solver iterations for structural stability
config.set_viterations(40);
config.set_piterations(40);

// Collisions: Enable soft body vs. rigid body and soft body vs. soft body collisions
config.set_collisions(0x11);

// Material stiffness (0 = loose, 1 = rigid)
const material = softBody.getMaterial(0);
material.set_m_kLST(0.1); // Linear stiffness
material.set_m_kAST(0.1); // Angular stiffness

// Damping and Pressure (The key to volumetric volume retention)
config.set_kDP(0.01);  // Damping coefficient
config.set_kDF(0.2);   // Dynamic friction
config.set_kPR(15.0);  // Pressure coefficient (internal pressure that pushes outward)

// Set total mass
softBody.setTotalMass(1.0, false);

// Disable deactivation so the physics stays active when resting
softBody.setActivationState(4); 

4. Bind the Physics to a Visual Mesh

To render the soft body using a graphics library like Three.js, you must map the positions of the soft body’s nodes (vertices) to the visual geometry on every frame.

Setup the Three.js Geometry

Use a matching Three.js BufferGeometry to render the soft body. Note that the vertex count of your Three.js mesh should ideally match or align with the physics soft body nodes.

const geometry = new THREE.BufferGeometry();
// ... Populate your geometry's positions and indices to match the physics ellipsoid ...
const material = new THREE.MeshPhongMaterial({ color: 0xff00ff });
const visualMesh = new THREE.Mesh(geometry, material);
scene.add(visualMesh);

Update Loop

During your render loop, step the physics world and copy the updated node positions from Ammo.js to your Three.js buffer attributes.

function updatePhysics(deltaTime) {
    // Step the physics world
    physicsWorld.stepSimulation(deltaTime, 10);

    // Get soft body nodes
    const nodes = softBody.get_m_nodes();
    const numNodes = nodes.size();
    
    // Get visual mesh vertex attributes
    const positions = visualMesh.geometry.attributes.position.array;

    let index = 0;
    for (let i = 0; i < numNodes; i++) {
        const node = nodes.at(i);
        const pos = node.get_m_x(); // Node position vector

        positions[index++] = pos.x();
        positions[index++] = pos.y();
        positions[index++] = pos.z();
    }

    // Tell Three.js to update the vertices and recompute normals for lighting
    visualMesh.geometry.attributes.position.needsUpdate = true;
    visualMesh.geometry.computeVertexNormals();
}