Skip to content
This repository

Direct port of the Bullet physics engine to JavaScript using Emscripten

branch: master
README.markdown

ammo.js

Demo: http://kripken.github.com/ammo.js/examples/new/ammo.html

Example code to give you an idea of the API: https://github.com/kripken/ammo.js/blob/master/examples/webgl_demo/ammo.html#L14

ammo.js is a direct port of the Bullet physics engine to JavaScript, using Emscripten. The source code is translated directly to JavaScript, without human rewriting, so functionality should be identical to the original Bullet.

'ammo' stands for "Avoided Making My Own js physics engine by compiling bullet from C++" ;)

ammo.js is zlib licensed, just like Bullet.

Discussion takes place on IRC at #emscripten on Mozilla's server (irc.mozilla.org)

Instructions

builds/ammo.js contains a prebuilt version of ammo.js. This is probably what you want.

You can also build ammo.js yourself, as follows:

Usage

The most straightforward thing is if you want to write your code in C++, and run that on the web. If so, then compile your code into LLVM, link it with bullet, and compile that to JavaScript using emscripten. (The easiest way to link it is to add your .bc file to the llvm-link command in make.py.)

If, on the other hand, you want to write code in JavaScript, you can use the autogenerated binding code. A complete example appears in

examples/hello_world.js

That is HelloWorld.cpp from Bullet, translated to JavaScript. Other examples in that directory might be useful as well. In particular see the WebGL demo code in

examples/webgl_demo/ammo.html

Bindings API

ammo.js autogenerates its API from the Bullet source code, so it should be basically identical. There are however some differences and things to be aware of:

  • All ammo.js elements should be accessed through Ammo.*. For example, Ammo.btVector3, etc., as you can see in the example code.

    Note however that by default ammo.js does not wrap builds in a closure - Ammo is just another name for |this|. The reason is that closure wrapping, while it keeps the global namespace clean, has significant performance downsides (currently 50% in the top engines). If you must, use a closure, but otherwise it is better not to. Note that it is a good idea to run ammo.js in a worker thread anyhow, in which case the global namespace is kept clean, and there is definitely no need for a closure (however, you must still be careful if you compile with the closure compiler, to avoid stepping on the minified names it generates).

    Accessing elements in ammo.js through Ammo.* is not strictly necessary in a normal (non-wrapped) build, but is recommended since it makes it easy to use a wrapped build without changing your code.

    If you do want to wrap a build in a closure, you can use wrap.py which is a little tool for that.

    Note that non-wrapped builds can lead to problems if the rest of your code uses global variables that collide with ammo.js's! You can run your code in JavaScript strict mode to see if that might be the problem, it will warn about your functions writing to global variables (but it won't notice your actual global variables accessed outside of functions). You can also check if this is the problem by wrapping an ammo.js build using wrap.py.

  • Member variables of structs and classes can be accessed through setter and getter functions, that are prefixed with |get_| or |set_|. For example,

    rayCallback.get_m_rayToWorld()

    will get m_rayToWorld from say a ClosestRayResultCallback. Native JavaScript getters and setters could give a slightly nicer API here, however their performance is potentially problematic.

  • Functions returning or getting float& or btScalar& are converted to float. The reason is that float& is basically float* with nicer syntax in C++, but from JavaScript you would need to write to the heap every time you call such a function, making usage very ugly. With this change, you can do |new btVector3(5, 6, 7)| and it will work as expected. If you find a case where you need the float& method, please file an issue.

  • Not all classes are exposed, for various reasons. Please file an issue if you find that something you need is missing.

  • Each call to |new X()| allocates a new object, sort of like how C++ |new| works. You need to manually free such objects, which can be done in JS using

    destroy(OBJECT)

    |delete| would have been a better parallel to C++, however |delete| is a reserved work in JS.

    Note that there is no way to allocate objects other than with |new X()|, unlike in C++ where you can define an object and it is held on the stack, and free'd automatically. (That might be possible with WeakMaps, but not enough browsers support it yet.) So in practice you need to remember to |destroy| the objects you create. However, you can let a lot of objects leak, in many cases, for example when you are done with using Ammo, there is really no need to free all the singleton objects you used. But, if you do create a lot of new objects while running Ammo during a long session, you should probably free those objects when possible.

    Note that currently destroying a btDiscreteDynamicsWorld fails for some reason (but normally you create a singleton of that, so you shouldn't really need to destroy it anyhow).

  • Functions that return an entire object, like |btQuaternion someFunc()|, will return a reference to a static object held inside the binding function. That means that you cannot call the binding function multiple times and still use the values - you must copy them.

  • All the bindings functions expect to receive wrapper objects, that contain the raw pointer inside them, and not a raw pointer (which is just a memory address - an integer). You should normally not need to deal with raw pointers, but if you do, the following functions can help:

    wrapPointer(ptr, Class) - Given a raw pointer (an integer), returns a wrapped object. Note that if you do not pass Class, it will be assumed to be |Object| - this is likely not what you want! getPointer(object) - Returns a raw pointer castObject(object, Class) - Returns a wrapping of the same pointer but to another class compare(object1, object2) - Compares two objects' pointers

    Note that there is always a single wrapped object for a certain pointer. This allows you to add data on that object and use it elsewhere, by using normal JavaScript syntax (object.attribute = someData etc.). Note that this almost means that compare() is not needed - since you can compare two objects of the same class, and if they have the same pointer they must be the same object - but not quite: The tricky case is where one is a subclass of the other, in which case the wrapped objects are different while the pointer is the same. So, the correct way to compare two objects is to call compare().

  • All the bindings functions that return pointers/references/objects will return wrapped pointers. The only potentially confusing case is when they are returning a null pointer. In that case, you will get NULL (a global singleton with a wrapped pointer of 0) instead of null (the JavaScript builtin object) or 0. The reason is that by always returning a wrapper, you can always take the output and pass it back to another binding function, without that function needing to check the type of the argument.

    If you want to pass a null pointer, use that NULL object. The reason is that the bindings code is faster if it does not need to check each argument for its type and convert them on the fly. In practice this should not be an inconvenience, and you shouldn't need to think about it, because bindings functions return wrapped objects, so you can just pass those back into other bindings functions. In other words, you should normally never have to see a raw pointer.

  • There is experimental support for binding operator functions. The following might work:

    Operator Name in JS
    = op_set
    + op_add
    - op_sub
    * op_mul
    / op_div
    [] op_get
    == op_eq
  • There is experimental support for callbacks from C++ back into JS. See tests/wrapping.js's use of ConcreteContactResultCallback. Basically you need to create an object of a particular (concrete, not abstract) class, and you can then customize it's vtable, replacing some virtual functions with others. Note that we do not have the type signature for functions at runtime (we can add it, but it would bloat the library), which means you will need to convert types manually, so if a parameter is a pointer or a reference to say a btVector3, you should do

    obj = Ammo.wrapPointer(arg, Ammo.btVector3)

    and for your return value, if it is a pointer or a reference, return

    obj.ptr

    Other types (ints, doubles) can be left as-is. Note also that as mentioned above this approach only works for concrete classes, not abstract ones. That is the purpose of ConcreteContactResultCallback. You can add similar things to root.h as needed.

Troubleshooting

  • It's easy to forget to write |new| when creating an object, for example

    var vec = Ammo.btVector3(1,2,3); // This is wrong! Need 'new'!

    This can lead to error messages like the following:

    Cannot read property 'a' of undefined Cannot read property 'ptr' of undefined

    This is an annoying aspect of JavaScript, sadly.

Reporting Issues

If you find a bug in ammo.js and file an issue, please include a script that reproduces the problem. That way it is easier to debug, and we can then include that script in our automatic tests.

Release Process

Pushing a new build in builds/ammo.js should be done only after the following steps:

  • Build a safe build and make sure it passes all automatic tests. Safe builds contain a lot of runtime assertions that can catch potential bugs (similar to the sort of things valgrind can catch).

  • Build a fast build and make sure it passes all automatic tests.

  • Run closure compiler on that fast build and make sure it passes all automatic tests.

  • Make sure that the stress test benchmark did not regress compared to the old build.

  • Run the WebGL demo in examples/webgl_demo and make sure it looks ok.

TODO

  • The HEAP memory space may not be implemented as a flat object in all JS engines, especially when we use a lot of memory. Need to investigate this.
Something went wrong with that request. Please try again.