How to Tear and Deform Soft Bodies in Ammo.js

Simulating realistic physics in web applications often requires advanced soft body dynamics, such as tearing cloth or permanently deforming squishy objects. This article provides a direct guide on how to implement tearing and plastic deformation on soft body meshes using the ammo.js physics library, detailing the configuration steps, key API methods, and code logic required to achieve these effects.


Part 1: Tearing a Soft Body Mesh

Tearing in ammo.js (a port of the Bullet Physics engine) involves breaking the structural connections (links) between the nodes (vertices) of a soft body when they undergo too much tension.

1. Enabling Native Tearing

Bullet has native support for tearing, primarily designed for cloth (patch) soft bodies. To enable this, you must configure the soft body’s configuration flags and set a tearing factor.

// Enable tearing flag in the soft body configuration
const sbConfig = softBody.get_m_cfg();
sbConfig.set_m_flags(sbConfig.get_m_flags() | 128); // 128 is typically the flag for tearing (f_Tearing)

// Set the tearing factor (m_dcf). 
// A lower value makes the material tear more easily under stress.
sbConfig.set_m_dcf(0.5); 

Because the WebAssembly bindings of ammo.js can sometimes limit direct access to internal tearing flags, manual tearing is often the most reliable method. This involves iterating through the links of the soft body, calculating the distance between linked nodes, and deleting links that exceed a threshold.

function updateTearing(softBody, tearThreshold) {
    const links = softBody.get_m_links();
    const numLinks = links.size();

    for (let i = numLinks - 1; i >= 0; i--) {
        const link = links.at(i);
        const node0 = link.get_m_n(0);
        const node1 = link.get_m_n(1);

        // Calculate current distance between nodes
        const pos0 = node0.get_m_x();
        const pos1 = node1.get_m_x();
        const distance = pos0.distance(pos1);

        // Compare against rest length (m_rl)
        const restLength = link.get_m_rl();
        if (distance > restLength * tearThreshold) {
            // Remove the link to tear the mesh
            links.erase(i); 
            // Force the soft body to rebuild its internal state
            softBody.set_m_bUpdateRtCst(true); 
        }
    }
}

Part 2: Implementing Plastic (Permanent) Deformation

By default, soft bodies in ammo.js are purely elastic—they will always attempt to return to their original shape defined by the initial “rest length” (m_rl) of their links. To make a soft body permanently deform (like clay or soft metal), you must implement plasticity.

Plasticity is achieved by permanently modifying the rest length of the links when they are stretched beyond an elastic yield limit.

Step-by-Step Implementation

  1. Set the Yield Limit: Define how much a link can stretch before it permanently deforms (e.g., 10% beyond rest length).
  2. Set the Plasticity Rate: Define how quickly the material conforms to its new shape (plastic flow).
  3. Update Rest Lengths: After each physics step, check all links and update their m_rl values.
function applyPlasticDeformation(softBody, yieldLimit = 1.1, plasticityRate = 0.5) {
    const links = softBody.get_m_links();
    const numLinks = links.size();

    for (let i = 0; i < numLinks; i++) {
        const link = links.at(i);
        const node0 = link.get_m_n(0);
        const node1 = link.get_m_n(1);

        // Calculate current distance between nodes
        const pos0 = node0.get_m_x();
        const pos1 = node1.get_m_x();
        const currentLength = pos0.distance(pos1);
        const restLength = link.get_m_rl();

        // Check if the link has stretched past the elastic yield limit
        if (currentLength > restLength * yieldLimit) {
            // Calculate the permanent deformation amount
            const deformation = currentLength - restLength;
            
            // Update the rest length permanently
            const newRestLength = restLength + (deformation * plasticityRate);
            link.set_m_rl(newRestLength);
        }
    }
}

Execution Loop

To make these effects active in your simulation, call these update functions inside your main animation loop directly after stepping the physics world:

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

    // Apply tearing and deformation to your soft body
    updateTearing(mySoftBody, 1.8); // Tears if stretched 180%
    applyPlasticDeformation(mySoftBody, 1.1, 0.2); // Deforms plastically

    // Render your scene
    renderer.render(scene, camera);
    requestAnimationFrame(tick);
}