# ammo.js: A Direct Emscripten Port of Bullet for JavaScript Physics

> ammo.js compiles the Bullet physics engine from C++ to JavaScript and WebAssembly, giving browser projects the same solver Bullet users already know. The API is autogenerated, so the friction sits in the bindings, not the physics.

**kripken/ammo.js** — Direct port of the Bullet physics engine to JavaScript using Emscripten

- Repository: https://github.com/kripken/ammo.js
- Stars: 4,574 · Forks: 580
- Language: C++
- License: NOASSERTION
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/kripken-ammo-js

## What ammo.js solves, and who it is actually for

The README states that ammo.js is a direct port of the Bullet physics engine to JavaScript using Emscripten, and that the source is translated directly without human rewriting, so functionality should be identical to the original Bullet. That sentence is the whole pitch. You are not getting a physics engine designed for JavaScript. You are getting Bullet, compiled.

The audience follows from that. If you already know Bullet's API from C++ or from a game engine, ammo.js lets you reuse that knowledge in a browser build. If you need Bullet-specific machinery such as soft bodies, the project ships WebGL demos for SoftBody-Rope, SoftBody-Cloth and SoftBody-Volume, plus a Heightmap demo and a Vehicle demo. Those are the areas where a hand-written JavaScript engine would be a large amount of work.

If instead you want a small, idiomatic JavaScript physics library, the same design decision that makes ammo.js faithful makes it awkward. The README is explicit that its API is autogenerated from Bullet, which means you inherit C++ naming, C++ object lifetimes and C++ struct access patterns. The project name itself is a joke about not writing a physics engine from scratch, and that framing tells you what it is not: it is not a JavaScript-first redesign.

## How the Emscripten bindings expose Bullet objects

The mechanism is a compile step. Bullet's C++ source lives in the bullet/ directory, ammo.idl describes which interfaces get wrapped, and Emscripten plus WebIDL generates the JavaScript bindings. The README points at the Emscripten WebIDL Binder wiki page for how the wrapped objects are meant to be used.

Several binding rules matter in day-to-day code. Everything is reached through Ammo.*, so you write Ammo.btVector3 rather than btVector3. Member variables on structs and classes are not plain properties; they go through getters and setters prefixed with get_ and set_, and the README gives rayCallback.get_m_rayToWorld() as the shape of that call. The README explains the choice: native JavaScript getters and setters could give a slightly nicer API, but their performance is potentially problematic.

There is one deliberate deviation from C++. Functions returning or taking float& or btScalar& are converted to plain float, because float& is effectively float* and calling such a function from JavaScript would otherwise mean writing to the heap on every call. The README says this is what makes new btVector3(5, 6, 7) work as expected, and asks for an issue if you hit a case where you genuinely need the float& method.

The biggest practical constraint is coverage. The README states that not all classes are exposed, only what is described in ammo.idl is wrapped, and it invites pull requests for extra things you need. So the API surface is a curated subset of Bullet, not the whole engine. There is also experimental support for binding operator functions, mapped to names like op_set, op_add, op_sub, op_mul, op_div, op_get and op_eq. The README says these might work, which is a fair warning for anything you build on top of them.

## Installing ammo.js and running a first simulation

The README says builds/ammo.js contains a prebuilt version and that this is probably what you want. package.json confirms the entry point: the main field is builds/ammo.js. The npm package name is ammo.js and the published version is 0.0.2.

For a first look, the README points at examples/hello_world.js, which it describes as HelloWorld.cpp from Bullet translated to JavaScript, and says other examples in that directory might be useful as well. In particular it points at the WebGL demo code in examples/webgl_demo/ammo.html. The README also links a Cubes demo and a separate Cubes (WebAssembly) demo, plus demos for soft body rope, soft body cloth, soft body volume, heightmap terrain and a vehicle.

Building from source needs Emscripten and cmake. The README gives these commands, which write into the builds directory.

```bash
cmake -B builds
cmake --build builds
```

Configuration options are cmake variables. The README documents CLOSURE=1 to compile with closure, TOTAL_MEMORY=268435456 to start with a 256MB heap against a default of 16MB, and ALLOW_MEMORY_GROWTH=0 for a fixed heap instead of the default resizable one.

```bash
cmake -B builds -DCLOSURE=1
cmake -B builds -DTOTAL_MEMORY=268435456
cmake -B builds -DALLOW_MEMORY_GROWTH=0
```

On Windows the README shows cmake's MinGW generator, and it notes that if Emscripten was not installed via the emsdk, its location can be set with -DEMSCRIPTEN_ROOT.

```bat
cmake -B builds -G 'MinGW Makefiles'
cmake --build builds
```

There is also a Docker path. The repository ships a Dockerfile based on emscripten/emsdk and a docker-compose.yml whose builder service runs cmake with CLOSURE=1. The README lists docker-compose build, docker-compose up and docker-compose run builder, and notes that adding cmake arguments means editing docker-compose.yml.

## Where the port leaks: coverage, memory and build size

The first limitation is the one the README states plainly: only what is in ammo.idl is wrapped. If your simulation needs a Bullet class that nobody added to that file, the JavaScript side does not have it, and your options are to submit a pull request or to build your own C++ with Emscripten instead. The README actually recommends that alternative for a different case: if you want to write your code in C++, build it with Emscripten normally and use Bullet either by linking it yourself or through emscripten-ports with -s USE_BULLET=1. In both of those cases, the README says, you do not need ammo.js. That is a candid admission that the project is not the right tool when the host language is already C++.

The second is memory. The default heap is 16MB according to the README, and the documented way to change it is TOTAL_MEMORY at configure time. There is a resizable heap by default, with ALLOW_MEMORY_GROWTH=0 available to make it fixed. That is the classic Emscripten trade-off: a growing heap is convenient but has its own behaviour, and a fixed heap is predictable but you must size it correctly up front. The README does not document what happens when a fixed heap is exhausted.

The third is build size, and the README treats it as a real concern rather than a footnote. It suggests removing unneeded interfaces from ammo.idl, naming btIDebugDraw and DebugDrawer as examples that are only needed for visual debug rendering, and removing methods from the -s EXPORTED_RUNTIME_METHODS=[] argument in make.py. UTF8ToString is given as an example that is only needed if you want printable error messages from DebugDrawer. Size reduction here is a manual editing exercise, not a flag.

Finally, the release situation. The repository shows no recent releases retrieved, and package.json still reads version 0.0.2. The last push to the default branch was on 2026-09-22, so the code is moving, but there is no tagged release cadence to pin against.

## ammo.js vs cannon.js and Rapier: three different bets

People searching for ammo.js alternatives usually land on cannon.js or Rapier, and the difference is architectural rather than a feature checklist.

cannon.js is a physics engine written in JavaScript by hand. Its API is idiomatic JavaScript, its objects are ordinary JavaScript objects, and you do not think about heaps or get_ and set_ prefixes. The cost is that it is a reimplementation: it does not carry Bullet's full feature set, and the soft body and vehicle demos that ammo.js ships have no direct equivalent in the same form.

Rapier takes a third route. It is a physics engine written in Rust, compiled to WebAssembly, with JavaScript bindings on top. Like ammo.js it is a compiled engine, but the bindings are designed for JavaScript rather than autogenerated from an existing C++ API. That is the practical difference you feel in code: Rapier's API reads as a JavaScript library, while ammo.js's API reads as Bullet with an Ammo. prefix.

The honest framing is that ammo.js wins when you specifically need Bullet semantics or Bullet's feature coverage, and when the people writing the simulation already know Bullet. It loses when the team is JavaScript-first and would spend more time fighting getters, setters and object lifetimes than writing gameplay. That is not a knock on the port; it is the predictable result of the design goal stated in the README, which was fidelity to the original rather than ergonomics.

## Testing, licence and the cost of keeping up

Tests run through npm. package.json defines npm test as test-js followed by test-wasm, and the README says this runs ava against both the JavaScript and WebAssembly builds. The two scripts set an AMMO_PATH environment variable to builds/ammo.js and builds/ammo.wasm.js respectively, which means the tests exercise the built artefacts rather than the source. If you rebuild with different cmake options, you are testing a different artefact than the one the project tests.

On licensing, the README states that ammo.js is zlib licensed, just like Bullet. The repository's LICENSE file is the authoritative text. Note that the licence identifier returned for this repository is NOASSERTION, which means the platform did not classify it automatically; read LICENSE yourself rather than trusting a badge. Because ammo.js is a port of Bullet rather than an independent work, the Bullet licence terms travel with it, and that is the thing to confirm before shipping a product. Nothing here is legal advice.

Upgrade cost is unusual for a JavaScript dependency. There are no retrieved releases and package.json sits at 0.0.2, so version pinning gives you little. The real upgrade surface is the build configuration: cmake options like CLOSURE, TOTAL_MEMORY and ALLOW_MEMORY_GROWTH, the contents of ammo.idl, and the EXPORTED_RUNTIME_METHODS list in make.py. If you trim any of those to reduce build size, you have forked the build inputs, and every future pull from upstream becomes a merge against your trimmed ammo.idl. That is the maintenance bill, and it is paid in build configuration rather than in package.json.

## Conclusion

Adopt ammo.js when you need Bullet's specific feature set (soft bodies, vehicles, heightmaps) in a browser and can accept a C++-shaped API. Do not adopt it if you want an idiomatic JavaScript physics library or a small bundle. Before committing, verify that the classes you need are actually wrapped in ammo.idl, check whether the prebuilt builds/ammo.js matches your required feature list, and confirm your licence obligations against the LICENSE file.

## FAQ

### What is ammo.js?

It is a direct port of the Bullet physics engine to JavaScript, built with Emscripten. The README states the source is translated directly without human rewriting, so functionality should be identical to the original Bullet.

### How do I install ammo.js and start using it?

The README says builds/ammo.js contains a prebuilt version and that this is probably what you want, and package.json lists builds/ammo.js as the main entry. The repository also documents building from source with cmake -B builds followed by cmake --build builds, or via the Dockerfile and docker-compose.yml.

### Why does ammo.js use get_ and set_ functions instead of normal properties?

Member variables of structs and classes are accessed through getter and setter functions prefixed with get_ and set_. The README explains that native JavaScript getters and setters could give a slightly nicer API, but their performance is potentially problematic.

### Are all Bullet classes available in ammo.js?

No. The README states that not all classes are exposed, only what is described in ammo.idl is wrapped, and it asks for pull requests adding extra classes that you need.

### What licence does ammo.js use?

The README states that ammo.js is zlib licensed, just like Bullet. The repository's LICENSE file is the authoritative text, and the platform's automatic classification for this repository is NOASSERTION.

### Can I write my physics code in C++ instead of JavaScript?

Yes, and in that case the README says you do not need ammo.js. You can build your C++ with Emscripten normally and link Bullet yourself, or use Bullet from emscripten-ports with -s USE_BULLET=1.

## Sources

- [Issues](https://github.com/kripken/ammo.js/issues)
- [kripken/ammo.js on GitHub](https://github.com/kripken/ammo.js)
- [README](https://github.com/kripken/ammo.js/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kripken-ammo-js
