How to Simulate a Balloon with Ammo.js Soft Body

Simulating a pressurized object like a balloon in a WebGL environment requires soft body physics that can handle internal forces and volume conservation. This article explains how to configure Ammo.js—the Emscripten port of the Bullet physics engine—to create a realistic, pressurized balloon. You will learn how to initialize a soft body dynamics world, generate an ellipsoid soft body, and tune specific pressure and stiffness coefficients to keep the balloon inflated during collisions.

1. Initialize the Soft Body Physics World

Unlike standard rigid body simulations, soft bodies in Ammo.js require specialized collision configurations and solvers. You must instantiate a btSoftRigidDynamicsWorld instead of the standard btDiscreteDynamicsWorld.

// Standard rigid body setup
const collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();

// Soft body solver
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.81, 0));

2. Generate the Balloon Geometry

To represent a balloon, you can use the built-in soft body helper to generate an ellipsoid. This utility automatically generates the nodes (vertices) and links (edges) required for the soft body simulation.

const softBodyHelpers = new Ammo.btSoftBodyHelpers();
const center = new Ammo.btVector3(0, 5, 0);
const radius = new Ammo.btVector3(1, 1.3, 1); // Elongated on the Y-axis like a balloon
const numVertices = 128; // Higher values yield smoother results but require more CPU power

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

3. Configure Pressure and Stiffness Parameters

By default, an ellipsoid soft body has no internal structure or pressure; it will collapse under its own weight or upon contact with other objects. To simulate a balloon, you must configure internal pressure parameters using the soft body configuration (m_cfg) and materials.

Enable Pressure and Volume Conservation

The critical parameter for balloon simulation is kPR (Pressure coefficient). Setting this value pushes the vertices outward, simulating compressed air inside.

const sbConfig = balloonBody.get_m_cfg();

// Increase solver iterations for stability
sbConfig.set_viterations(10); // Velocity solver iterations
sbConfig.set_piterations(10); // Position solver iterations

// Set the pressure coefficient (higher values mean higher inflation pressure)
sbConfig.set_kPR(15.0); 

// Enable pose-matching and volume conservation flags
// fpreset flags: 1 = volume conservation, 2 = frame conservation
sbConfig.set_kDF(0.1); // Dynamic friction coefficient
sbConfig.set_kDP(0.01); // Damping coefficient

Apply Material Stiffness

The skin of a balloon is elastic but retains some resistance to stretching. You must fetch the default material of the soft body and adjust its stiffness.

// Fetch the default material
const material = balloonBody.get_m_materials().at(0);

material.set_m_kLST(0.9); // Linear stiffness coefficient (0 to 1)
material.set_m_kAST(0.9); // Angular stiffness coefficient (0 to 1)

Generate Bending Constraints

To prevent the balloon from folding in on itself when colliding, generate bending constraints. This creates virtual structural links across the interior of the balloon.

// Parameter 2 is the distance of the neighbor nodes to link
balloonBody.generateBendingConstraints(2, material);

4. Set the Default Pose

To make the volume-conserving algorithms work correctly, you must tell the physics engine that the current shape of the balloon is its “default” inflated shape.

// Set pose: first argument is volume conservation, second is frame preservation
balloonBody.setPose(true, true);

5. Add the Balloon to the World

Once configured, the soft body is ready to be added to your dynamics world. If you want the balloon to collide with other rigid or soft bodies, ensure you configure collision masks properly.

// Disable individual node collisions to save performance, let the shape collide as a whole
balloonBody.m_cfg.set_collisions(0x11); // Enable rigid versus soft body collisions

// Add to the physics world
physicsWorld.addSoftBody(balloonBody);

6. Update and Render

During your animation render loop, step the physics world forward and update your graphic mesh’s vertices to match the positions of the soft body nodes.

function updatePhysics(deltaTime) {
    physicsWorld.stepSimulation(deltaTime, 10);

    // Map soft body nodes to your Three.js or custom WebGL mesh geometry
    const nodes = balloonBody.get_m_nodes();
    const numNodes = nodes.size();
    
    // Iterate through the nodes to update vertex positions of your graphic mesh
    for (let i = 0; i < numNodes; i++) {
        const node = nodes.at(i);
        const pos = node.get_m_x(); // Current position of the node
        
        // Update your visual renderer's buffer with pos.x(), pos.y(), pos.z()
    }
}