How to Use and Scale btMultiSphereShape in Ammo.js
This article provides a direct guide on how to instantiate and scale
a btMultiSphereShape within an Ammo.js physics simulation.
You will learn how to define multiple spheres with distinct positions
and radii to create complex composite collision shapes, manage
WebAssembly memory allocations for these shapes, and dynamically apply
scaling factors to them during runtime.
Instantiating a btMultiSphereShape
The btMultiSphereShape represents a collision volume
defined by an array of spheres, each with its own local position and
radius. This shape is ideal for representing non-uniform, organic
volumes like capsules with varying thicknesses, dumbbells, or simplified
character physics hulls.
Because Ammo.js is a WebAssembly port of the C++ Bullet Physics
engine, you cannot pass native JavaScript arrays directly to its
constructors. Instead, you must allocate memory on the Emscripten heap
for both the positions (btVector3 array) and the radii
(float array).
Here is how to instantiate a btMultiSphereShape
containing two spheres:
// Define the spheres' local positions and radii
const spheres = [
{ x: 0, y: -1, z: 0, radius: 1.0 },
{ x: 0, y: 1, z: 0, radius: 1.5 }
];
const numSpheres = spheres.length;
// 1. Allocate memory for btVector3 positions array (16 bytes per btVector3 alignment)
const vec3Size = 16; // Ammo.btVector3 size on the heap
const posBuffer = Ammo._malloc(numSpheres * vec3Size);
// 2. Allocate memory for float (btScalar) radii array (4 bytes per float)
const scalarSize = 4;
const radBuffer = Ammo._malloc(numSpheres * scalarSize);
// 3. Populate the allocated memory buffers
for (let i = 0; i < numSpheres; i++) {
const s = spheres[i];
// Create temporary Ammo vector and copy it into the heap buffer
const tempVec = new Ammo.btVector3(s.x, s.y, s.z);
// Calculate the heap offset for the current vector
const vecPtr = posBuffer + (i * vec3Size);
// Write x, y, z values directly to the WebAssembly memory
Ammo.HEAPF32[vecPtr >> 2] = tempVec.x();
Ammo.HEAPF32[(vecPtr + 4) >> 2] = tempVec.y();
Ammo.HEAPF32[(vecPtr + 8) >> 2] = tempVec.z();
// Write radius to the radii buffer
Ammo.HEAPF32[(radBuffer + (i * scalarSize)) >> 2] = s.radius;
// Clean up temporary vector
Ammo.destroy(tempVec);
}
// 4. Instantiate the shape using the heap pointers
const multiSphereShape = new Ammo.btMultiSphereShape(posBuffer, radBuffer, numSpheres);
// 5. Free the allocated heap memory now that the shape is created
Ammo._free(posBuffer);
Ammo._free(radBuffer);Scaling a btMultiSphereShape
To scale a btMultiSphereShape dynamically within your
simulation, use the setLocalScaling method inherited from
the base btCollisionShape class. This method accepts an
Ammo.btVector3 representing the scaling factors along the
X, Y, and Z axes.
// Define the scale vector (e.g., scale x and z by 1.5, and y by 2.0)
const scaleVector = new Ammo.btVector3(1.5, 2.0, 1.5);
// Apply the scale to the multi-sphere shape
multiSphereShape.setLocalScaling(scaleVector);
// Clean up the scale vector from memory
Ammo.destroy(scaleVector);Updating the Physics World After Scaling
If the btMultiSphereShape is already attached to an
active btRigidBody in your physics world, applying a new
scale will not automatically update the bounding volume or the rigid
body’s inertia tensor. You must manually force the physics world to
recalculate these values:
// Assuming 'rigidBody' is the body using your multiSphereShape
// and 'physicsWorld' is your Ammo.btDiscreteDynamicsWorld instance
// 1. Recalculate local inertia for the scaled shape
const localInertia = new Ammo.btVector3(0, 0, 0);
const mass = rigidBody.getInvMass() > 0 ? 1.0 / rigidBody.getInvMass() : 0;
if (mass > 0) {
multiSphereShape.calculateLocalInertia(mass, localInertia);
rigidBody.setMassProps(mass, localInertia);
}
// 2. Update the broadphase collision AABB (Axis-Aligned Bounding Box)
physicsWorld.updateSingleAABB(rigidBody);
// 3. Clean up
Ammo.destroy(localInertia);