Implementing Buoyancy in Ammo.js Physics

This article explores how the ammo.js physics library handles experimental buoyancy and basic fluid interactions. Because ammo.js is a direct JavaScript port of the C++ Bullet Physics engine, it focuses primarily on rigid body dynamics and does not feature a built-in, real-time fluid solver. Instead, developers simulate buoyancy and fluid drag by combining collision triggers, calculating submerged volumes, and manually applying custom force vectors to rigid bodies.

The Core Architecture of Ammo.js Fluids

Standard builds of ammo.js do not include active Smoothed Particle Hydrodynamics (SPH) or native Navier-Stokes solvers due to performance constraints in browser environments. Instead, the library relies on approximation methods. To simulate an object floating in water, developers must manually bridge the gap between collision detection and custom force application.

This approximation is achieved through a three-step pipeline: detection, force calculation, and damping.

Step 1: Fluid Volume Detection (Ghost Objects)

To simulate fluid, you must define the boundaries of the liquid. In ammo.js, this is represented by a sensor volume or a “Ghost Object” (btGhostObject).

Step 2: Applying Buoyancy Forces

Once a rigid body is detected inside the fluid volume, you must manually apply an upward force to counteract gravity. This relies on Archimedes’ principle: the upward buoyant force is equal to the weight of the fluid displaced by the object.

To implement this in ammo.js:

  1. Calculate Submergence Depth: Determine how far below the fluid’s surface plane the rigid body’s center of mass is located.
  2. Determine Displaced Volume: Calculate a scalar multiplier based on how much of the object is submerged (e.g., 0% at the surface, 100% when fully underwater).
  3. Apply Central Force: Apply an upward force vector using the applyCentralForce() method on the rigid body.

The formula used in the physics tick callback looks like this:

\[\vec{F}_{buoyancy} = \vec{u} \times (\rho \times V_{submerged} \times g)\]

Where \(\vec{u}\) is the upward vector, \(\rho\) is the fluid density, \(V_{submerged}\) is the volume submerged, and \(g\) is the gravity constant.

Step 3: Simulating Fluid Drag and Viscosity

An upward force alone will cause a floating object to bob up and down indefinitely. To make the interaction realistic, you must simulate water resistance (drag).

ammo.js handles this through damping properties. When an object enters the fluid volume, you must dynamically increase its linear and angular damping factors using:

rigidBody.setDamping(linearDamping, angularDamping);

When the object exits the fluid volume, these values must be restored to their default atmospheric settings.

Experimental Soft Body and Particle Approaches

For visual fluid interactions, developers sometimes leverage the experimental soft body dynamics in ammo.js (btSoftBody). Deformable meshes can be configured with low stiffness to mimic gelatinous surfaces or basic soft-body fluid envelopes.

Alternatively, SPH particle simulations can be managed in external JavaScript code, using ammo.js rigid bodies as kinematic colliders that push particles aside. However, for standard gameplay and physics interactions, the rigid body trigger-and-force method remains the most performant and reliable choice.