Ammo.js Precision Issues in Large Coordinate Systems
Using 32-bit floating-point numbers in ammo.js—the Emscripten port of the Bullet physics engine—for large-scale coordinate systems introduces significant numerical precision limitations. As game worlds expand and objects move far from the origin (0,0,0), the mathematical accuracy of single-precision floats degrades exponentially. This article explains how this loss of precision manifests as physics jitter, collision detection failures, and constraint instability, and outlines the standard techniques developers use to resolve these issues.
The Limits of 32-Bit Floating-Point Numbers
Ammo.js represents coordinates, velocities, and bounding boxes using IEEE 754 single-precision (32-bit) floats. A 32-bit float allocates 23 bits to its significand (mantissa), which provides roughly 7 decimal digits of precision.
Near the coordinate origin, this precision is highly granular, allowing for sub-millimeter accuracy. However, as coordinate values grow larger, the gap between representable floating-point numbers increases. For example: * At 1.0 unit from the origin, the spacing between consecutive float values is approximately \(1.19 \times 10^{-7}\) units. * At 10,000 units from the origin, the spacing grows to roughly \(0.00097\) units (nearly 1 millimeter). * At 100,000 units from the origin, the spacing is about \(0.0078\) units (nearly 8 millimeters).
This loss of resolution directly degrades the mathematical calculations underpinning the physics simulation.
Physics Jitter and Visual Shaking
The most immediate symptom of float precision loss is visual jitter. Ammo.js calculates object movement by multiplying velocity by a small time step (e.g., \(1/60\) of a second). When an object is far from the origin, the calculated positional increment may be smaller than the minimum representable gap between floating-point numbers at that distance.
As a result, the physics engine is forced to round the object’s position to the nearest representable float. Instead of smooth movement, the object teleports discretely from one float grid intersection to another, causing a noticeable stuttering or “shaking” effect during rendering.
Collision Detection Failures and Tunneling
Collision detection in ammo.js relies on precise geometric algorithms, such as the GJK (Gilbert-Johnson-Keerthi) algorithm for convex shapes and Sweep-and-Prune for broadphase collision detection. These algorithms compute distances, normals, and penetration depths.
When coordinates are extremely large: * Inaccurate Bounding Boxes: The Axis-Aligned Bounding Boxes (AABBs) calculated for distant objects lose precision, leading to overlapping bounds that do not exist or missed overlaps. * Tunneling: Fast-moving objects can pass completely through thin colliders (like walls or terrain floors) because the collision solver cannot accurately compute the intersection points within the bloated floating-point grid. * False Collisions: Rounding errors can cause the solver to register collisions that are centimeters away from the actual mesh surface, resulting in objects bouncing off invisible barriers.
Constraint and Joint Instability
Physics constraints—such as hinges, sliders, and point-to-point joints—rely on solving complex systems of linear equations to keep connected bodies together. The constraint solver in ammo.js uses iterative methods to satisfy these joint limits.
When the coordinates of the joint anchors are situated far from the origin, the mathematical Jacobian matrices used by the solver suffer from severe numerical drift. Small rounding errors accumulate rapidly over multiple simulation steps, causing joints to stretch, drift out of alignment, vibrate violently, or “explode” by launching connected rigid bodies apart at high velocities.
Mitigation Strategies
Because JavaScript and WebAssembly environments running ammo.js are practically restricted to 32-bit floats for performance reasons, developers must bypass these physical limitations using architectural design:
- Floating Origin (Origin Shifting): The most common solution is to keep the player or camera at (0,0,0) in the physics world. When the player moves past a certain threshold, the engine shifts the coordinates of all active physical bodies in the opposite direction, pulling the active simulation area back to the high-precision region near the origin.
- World Partitioning: Large worlds are divided into smaller, self-contained grid cells or zones. Only the active zone surrounding the player is loaded into the ammo.js physics world at any given time, ensuring coordinates never exceed safe precision thresholds.
- Physics Scaling: Scaling down the units of the physics simulation (e.g., treating 1 unit as 10 meters instead of 1 meter) can keep coordinate values smaller, though this requires careful tuning of gravity, forces, and masses to maintain realistic behavior.