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' 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)
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:
and set it up. See
Run the build script,
which should generate builds/ammo.js.
Optionally, run the automatic tests,
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.
Member variables of structs and classes can be accessed through setter and getter functions, that are prefixed with
|set_|. For example,
m_rayToWorldfrom say a
Functions returning or getting
btScalar&are converted to float. The reason is that
|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
|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
btDiscreteDynamicsWorldfails 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
object.attribute = someDataetc.). 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
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
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
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.
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
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.
Pushing a new build in
builds/ammo.js should be done only after the
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.
- 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.