Append Nodes to Ammo.js Soft Body Rope Dynamically

This article explains the technical process of dynamically appending a new node to an existing soft body rope in the Ammo.js physics engine. Since the WebIDL bindings of Ammo.js do not natively support resizing a soft body’s node array at runtime, achieving this requires specific workarounds. We will cover the two most viable methods: recreating the rope while preserving physical state, and utilizing a pre-allocation pool.

The Core Limitation of Ammo.js Soft Bodies

In Ammo.js (a port of Bullet Physics), a soft body rope is represented by a btSoftBody object containing a fixed buffer of nodes (vertices) and links (edges). These buffers are allocated in WebAssembly memory when the rope is initialized, typically via btSoftBodyHelpers.CreateRope. Because memory is statically allocated for the simulation structure, you cannot simply push a new node into the active rope’s memory array at runtime without causing memory corruption or physics crashes.

To dynamically append a node, you must implement one of the two strategies below.


This method involves destroying the existing rope and instantly replacing it with a new, longer rope. To prevent visual stuttering or physical jolts, you must copy the exact positions and velocities of the old nodes to the new rope.

Step 1: Capture the Current State

Before removing the old rope, query its nodes to extract their current positions, previous positions, and velocities.

const nodeCount = oldRope.get_m_nodes().size();
const states = [];

for (let i = 0; i < nodeCount; i++) {
    const node = oldRope.get_m_nodes().at(i);
    states.push({
        position: [node.get_m_x().x(), node.get_m_x().y(), node.get_m_x().z()],
        velocity: [node.get_m_v().x(), node.get_m_v().y(), node.get_m_v().z()]
    });
}

Step 2: Calculate the Position of the New Node

Determine where the \(N+1\) node should be placed. This is typically calculated based on the position of the last node (\(N\)) plus a directional offset vector multiplied by the desired link length.

Step 3: Recreate the Rope

Remove the old rope from the physics world and destroy it to free WebAssembly memory.

physicsWorld.removeSoftBody(oldRope);
Ammo.destroy(oldRope);

Next, create a new rope with \(N+1\) nodes using the helper:

const softBodyHelpers = new Ammo.btSoftBodyHelpers();
const newRope = softBodyHelpers.CreateRope(
    physicsWorld.getWorldInfo(),
    startPoint, // Temporary start
    endPoint,   // Temporary end
    nodeCount + 1,
    0 // fixeds mask
);

Step 4: Inject the Stored State and Apply the New Node

Iterate through the new rope’s nodes. Apply the saved positions and velocities to the first \(N\) nodes. Set the position of the \((N+1)\)th node to your calculated coordinates.

for (let i = 0; i < nodeCount; i++) {
    const node = newRope.get_m_nodes().at(i);
    
    // Set Position
    node.get_m_x().setValue(states[i].position[0], states[i].position[1], states[i].position[2]);
    node.get_m_q().setValue(states[i].position[0], states[i].position[1], states[i].position[2]); // Previous position
    
    // Set Velocity
    node.get_m_v().setValue(states[i].velocity[0], states[i].velocity[1], states[i].velocity[2]);
}

// Set position for the newly appended node (index nodeCount)
const newNode = newRope.get_m_nodes().at(nodeCount);
newNode.get_m_x().setValue(newX, newY, newZ);
newNode.get_m_q().setValue(newX, newY, newZ);
newNode.get_m_v().setValue(0, 0, 0); // Start the new node at rest relative to the rope

Step 5: Re-add to the Physics World

Configure the physical properties of the new rope (such as total mass, stiffness, and collision flags) to match the old rope, and add it back to the physics world.


If you need to append nodes frequently (e.g., a growing vine or a lasso), reconstructing the rope repeatedly is computationally expensive. Instead, pre-allocate a rope with the maximum number of nodes you anticipate needing.

Step 1: Initialize with Inactive Nodes

Create a rope with \(M\) nodes (where \(M\) is the maximum length). Place the “unused” nodes at the exact same spatial coordinate as the current terminal node of your active rope.

Step 2: Zero Out Mass of Inactive Nodes

Set the mass of all inactive nodes to zero. In Bullet Physics, a node with zero mass acts as an infinite mass (static) node, effectively freezing it in place and preventing it from responding to gravity or spring forces.

Step 3: Activate Nodes Dynamically

When you need to append a node: 1. Locate the first inactive node in the sequence. 2. Position it at the desired offset relative to the active end. 3. Assign it a positive mass value (e.g., 1.0 / totalActiveNodes). 4. Update the soft body’s mass distribution using internal Ammo.js updates so the solver registers the change.