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); - Pressure (
kPR): This is the most crucial setting. A high pressure value keeps the sphere filled with “air,” preventing it from collapsing flat onto the floor when gravity acts on it. - Stiffness (
kLST&kAST): Low stiffness values allow the body to deform dramatically upon impact, creating the jelly jiggle effect.
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();
}