How Ammo.js Handles Soft Body Self-Collision

This article explores how the ammo.js physics engine—the JavaScript port of the Bullet physics library—manages self-collision within complex soft bodies. We will examine the underlying algorithms, key configuration settings such as cluster-based collisions, and how developers can enable and optimize this feature to prevent soft body self-penetration.

The Challenge of Soft Body Self-Collision

In physics simulation, a soft body (such as cloth, rope, or a deformable volume) is represented as a network of vertices (nodes) connected by structural springs (links). By default, ammo.js calculates collisions between the soft body and external rigid bodies, but it does not check if the soft body is colliding with itself. Without self-collision enabled, a folding piece of cloth or a bending gel-like structure will pass through its own geometry, destroying the realism of the simulation.

Resolving self-collision is computationally expensive because the engine must constantly monitor whether any part of the soft body’s deformable mesh is intersecting with another part of the same mesh.

The Core Mechanism: Clusters vs. Vertex-Face Detection

Ammo.js handles soft body self-collision primarily through two methods: Vertex-Face (VF) collisions and Cluster-Based collisions.

1. Vertex-Face (VF) Self-Collision

In this approach, the engine checks every vertex (node) against every triangular face of the soft body’s mesh. While highly accurate, this method is computationally demanding. For complex meshes with thousands of vertices, Vertex-Face self-collision can cause severe performance drops, making it impractical for real-time web applications.

To solve the performance bottleneck of VF collision, Bullet and ammo.js utilize a technique called deformable clusters.

How to Configure Self-Collision in Ammo.js

To enable self-collision in ammo.js, developers must configure the soft body’s configuration object (btSoftBodyConfig) and generate clusters.

Step 1: Set the Collision Flags

You must modify the soft body’s collision flags to instruct the solver to look for soft-body-to-soft-body and self-collisions. This is done using the m_cfg.collisions bitmask.

// Example flag configuration
const config = softBody.get_m_cfg();

// Clear existing flags and set cluster-to-cluster soft body collision
config.set_collisions(0x0001 | 0x0020); 

In the underlying Bullet API, the key flags for self-collision are: * fCollision::CL_SS: Enables cluster-versus-cluster collisions between soft bodies. * fCollision::CL_SELF: Enables cluster-versus-cluster self-collisions within the same soft body.

Step 2: Generate Clusters

If you choose cluster-based collision, you must generate the clusters after creating the soft body mesh. This partition is built using the generateClusters method.

// Generate 1024 clusters (or any number based on mesh complexity)
softBody.generateClusters(1024);

Choosing the right number of clusters is a balancing act. Too few clusters will result in inaccurate self-collision (as the clusters will be too large and rigid), while too many clusters will decrease performance.

Step 3: Increase Solver Iterations

Because self-collision introduces complex constraints, you may need to increase the position solver iterations to ensure the body does not jitter or collapse under stress.

config.set_piterations(4); // Increase position solver iterations (default is 1)

Performance Optimization

Because self-collision calculations scale poorly with mesh complexity, optimizing your implementation is essential for maintaining 60 FPS in WebGL environments: