527 lines
20 KiB
Markdown
527 lines
20 KiB
Markdown
# MuJoCo JavaScript Bindings
|
||
|
||
These are the canonical JavaScript and TypeScript bindings for the MuJoCo
|
||
physics engine.
|
||
|
||
This package provides a high-level API that allows you to interact with the core
|
||
MuJoCo engine compiled into a high-performance WebAssembly (WASM) module. These
|
||
bindings are developed and maintained by Google DeepMind and are always up to
|
||
date with the latest developments in MuJoCo. For brevity, the documentation
|
||
below will often refer to “JavaScript” but the concepts apply equally to
|
||
TypeScript.
|
||
|
||
> [!IMPORTANT]
|
||
> _These bindings are still a WIP. For details, see the [Future Work](#future-work)
|
||
> section. Also note that development has primarily taken place on Linux using
|
||
> Google Chrome. If you're working on a different OS or browser, you may
|
||
> encounter some rough edges. We have successfully tested the bindings on MacOS
|
||
> in CI but as of November 13th 2025, Windows support remains experimental
|
||
> (installation succeeded on one Windows 11 machine but failed on others)._
|
||
|
||
## Installation
|
||
|
||
The easiest way to use the MuJoCo JavaScript bindings is to install the
|
||
`@mujoco/mujoco` package from npm:
|
||
|
||
```sh
|
||
npm install @mujoco/mujoco
|
||
```
|
||
|
||
This package is ESM (`type: module`) and includes the pre-compiled WebAssembly
|
||
module, JavaScript bindings, and TypeScript declarations. Ensure your bundler or
|
||
dev server serves the `.wasm` asset at runtime.
|
||
|
||
### Threading Models
|
||
|
||
The `@mujoco/mujoco` package includes two distinct builds of the engine to
|
||
support different browser environments and performance needs.
|
||
|
||
#### 1. Single-Threaded (Default)
|
||
|
||
The standard single-threaded version is located at the root of the package. It
|
||
is compatible with all modern browsers and does not require special security
|
||
headers.
|
||
```typescript
|
||
import loadMujoco from '@mujoco/mujoco';
|
||
```
|
||
|
||
#### 2. Multi-Threaded (MT)
|
||
|
||
The multi-threaded version is located in the `/mt` subfolder. It utilizes Web
|
||
Workers and `SharedArrayBuffer` to parallelize physics computations.
|
||
```typeScript
|
||
import loadMujoco from '@mujoco/mujoco/mt';
|
||
```
|
||
|
||
> [!NOTE]
|
||
> Due to the use of `SharedArrayBuffer`, browsers require Cross-Origin Isolation
|
||
> to enable multi-threading. Your web server must send the following HTTP
|
||
> headers:
|
||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||
>
|
||
> If these headers are missing, the module will fail to initialize.
|
||
|
||
## Build from source
|
||
|
||
### Prerequisites
|
||
|
||
> [!NOTE]
|
||
> Run all the commands in this README from the top-level directory.
|
||
|
||
- To compile the [`bindings.cc`](codegen/generated/bindings.cc) file, which
|
||
generates the `.wasm` WebAssembly file, `.js` JavaScript import, and `.d.ts`
|
||
TypeScript declaration file, you will need Emscripten SDK version `4.0.10`.
|
||
Later versions may work but are untested. To set up the SDK, do the
|
||
following, you can run this anywhere but the rest of the commands in this
|
||
README only work in the shell where you source the `emsdk_env.sh` script.
|
||
|
||
```sh
|
||
git clone https://github.com/emscripten-core/emsdk.git
|
||
./emsdk/emsdk install 4.0.10
|
||
./emsdk/emsdk activate 4.0.10
|
||
source ./emsdk/emsdk_env.sh
|
||
```
|
||
|
||
- To easily run the JavaScript tests and the demo application, `node` and `npm`
|
||
are required. We recommend managing these using
|
||
[nvm](https://github.com/nvm-sh/nvm). There are also various JavaScript
|
||
dependencies needed for the tests, demo, and bindings build process. These
|
||
dependencies are expected to be located in the `wasm` folder. To install
|
||
them and ensure they can be found by later commands, run the following:
|
||
|
||
```sh
|
||
npm install --prefix ./wasm
|
||
export PATH="$(pwd)/wasm/node_modules/.bin:$PATH"
|
||
```
|
||
|
||
- To modify the bindings `python3` is required because the [`bindings.cc`](codegen/generated/bindings.cc)
|
||
file is generated by a Python script. To run the bindings generator tests,
|
||
`absl` is required and `pytest` will be helpful. Set up a Python environment
|
||
with these dependencies as follows:
|
||
|
||
```sh
|
||
python3 -m venv .venv
|
||
source .venv/bin/activate
|
||
pip install -r python/build_requirements.txt
|
||
```
|
||
|
||
> [!TIP]
|
||
> _Emscripten is well-documented. We recommend reading the sections covering the
|
||
> [Emscripten Compiler Settings](https://emscripten.org/docs/tools_reference/settings_reference.html),
|
||
> the [Emscripten SDK](https://emscripten.org/docs/tools_reference/emsdk.html),
|
||
> and the [Embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html)
|
||
> library. To understand the limitations and caveats related to using the
|
||
> browser as a platform, see the
|
||
> [Porting](https://emscripten.org/docs/porting/index.html#porting) section._
|
||
|
||
The [`bindings.cc`](codegen/generated/bindings.cc) file is compiled to generate
|
||
the `.wasm` WebAssembly file, `.js` JavaScript import, and `.d.ts` TypeScript
|
||
declaration file. These are the files you'll use to call MuJoCo from JavaScript.
|
||
To generate them ensure the npm and Emscripten SDK prerequisites are set up and
|
||
then run the following:
|
||
|
||
```sh
|
||
emcmake cmake -B build && cmake --build build
|
||
```
|
||
|
||
This command will generate the following folders under the project root:
|
||
|
||
- `build`: contains MuJoCo compiled using Emscripten.
|
||
- `wasm/dist`: contains the WebAssembly module, `.js` and `.d.ts` files.
|
||
|
||
The assets inside those folders are compiled and prepared to run as
|
||
single-threaded. If you need to operate with a multi-threaded version of the
|
||
module make sure to pass the `-DMUJOCO_WASM_THREADS=ON` flag like:
|
||
|
||
```sh
|
||
emcmake cmake -B build -DMUJOCO_WASM_THREADS=ON && cmake --build build
|
||
```
|
||
|
||
### Example Application
|
||
|
||
After generating the bindings you will be ready to write web applications using
|
||
MuJoCo. We have provided a basic web application that uses Three.js to render a
|
||
simple simulation, to try it run this command:
|
||
|
||
```sh
|
||
npm run dev:demo --prefix ./wasm
|
||
```
|
||
|
||
You may prefer to write your entire app in C++ and compile it using Emscripten.
|
||
If you do this, you won’t need to use these bindings, since you’ll be writing
|
||
minimal JavaScript, and the granularity of these bindings may be inappropriate
|
||
(e.g., you might want to call multiple MuJoCo functions in the C++ callback
|
||
invoked by `requestAnimationFrame`).
|
||
|
||
We have also found that a hybrid approach can be helpful, as it is often more
|
||
convenient to work with browser APIs directly in JavaScript. If you choose to
|
||
write your application in C++ and compile it using Emscripten, you may want to
|
||
copy a subset of the `EMSCRIPTEN_BINDINGS` from `bindings.cc` into your
|
||
application’s source file.
|
||
|
||
## User Guide
|
||
### Named Access
|
||
|
||
The bindings support named access methods, similar to the Python bindings,
|
||
allowing convenient access to model and data elements by name or index. For
|
||
example, you can access a geometry by name using `model.geom('mygeom')` or a
|
||
joint using `data.jnt('myjoint')`.
|
||
|
||
For more details and examples of how to use named access, please refer to the [named access tests](tests/bindings_test.ts#L1876-L2378) and [documentation](https://mujoco.readthedocs.io/en/stable/python.html#named-access).
|
||
|
||
### Memory Management
|
||
Embind-wrapped C++ object handles created or returned into JavaScript live on
|
||
the WebAssembly heap and are **not** garbage-collected by the JS runtime.
|
||
|
||
Any heap-allocated C++ object exposed to JS (e.g. via `new Module.MyClass(...)`
|
||
or returned as a pointer/reference from a binding) must be explicitly freed
|
||
when no longer needed to avoid memory leaks.
|
||
|
||
Use the generated `.delete()` method on wrapped instances to destroy the
|
||
underlying C++ object:
|
||
|
||
```typescript
|
||
const obj = new Module.MyClass(...);
|
||
// ... use obj ...
|
||
obj.delete(); // free the C++ memory
|
||
```
|
||
|
||
Be careful to call `.delete()` exactly once per created object (double-delete
|
||
is an error). In JS code paths that may throw or return early, ensure
|
||
deletion happens in finally blocks or wrap lifetime management to avoid leaks.
|
||
|
||
> [!IMPORTANT]
|
||
> _Embind's documentation strongly recommends that JavaScript code explicitly
|
||
> deletes any C++ object handles it has received._
|
||
|
||
### Copy vs. Reference
|
||
|
||
When interacting with MuJoCo objects through the WASM bindings, it's important
|
||
to understand how data is accessed. Properties on objects like `MjModel` and
|
||
`MjData` can expose data in two ways: by copy or by reference.
|
||
|
||
#### 1. By Copy (Value-based access)
|
||
|
||
Some properties return a copy of the data at the time of access. This is common
|
||
for complex data structures that need to be marshalled from C++ to JavaScript.
|
||
|
||
A key example is `MjData.contact`. When you access `data.contact`, you get an
|
||
object containing a copy of the contacts at that specific moment in the
|
||
simulation.
|
||
|
||
If you step the simulation forward, they will not be updated. You must access
|
||
`data.contact` again to get the new contact information.
|
||
|
||
The object you get is a JavaScript proxy interface generated by [Emscripten’s Embind library](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#built-in-type-conversions) when you expose a `std::vector` using `register_vector<T>`. It is essentially a "bridge" object.
|
||
|
||
```typescript
|
||
export interface MjContactVec extends ClassHandle {
|
||
/** Appends a new element to the end of the vector, increasing its length by one. */
|
||
push_back(_0: MjContact): void;
|
||
/** Resizes the vector to contain the specified number of elements, filling new slots with the provided value. */
|
||
resize(_0: number, _1: MjContact): void;
|
||
/** Returns the total number of elements currently stored in the vector. */
|
||
size(): number;
|
||
/** Retrieves the element at the specified index, or returns undefined if the index is out of bounds. */
|
||
get(_0: number): MjContact | undefined;
|
||
/** Overwrites the element at the specified index; returns true if successful or false if the index is invalid. */
|
||
set(_0: number, _1: MjContact): boolean;
|
||
}
|
||
```
|
||
|
||
Example:
|
||
```typescript
|
||
// Gets contacts at the current time.
|
||
const contacts = data.contact;
|
||
|
||
// Step the simulation
|
||
mujoco.mj_step(model, data);
|
||
|
||
// `contacts` is now stale. To get the new contacts, you must access the property again:
|
||
const newContacts = data.contact;
|
||
|
||
// Remember to delete all created objects when they are no longer needed.
|
||
contacts.delete();
|
||
newContacts.delete();
|
||
```
|
||
|
||
#### 2. By Reference (View-based access)
|
||
|
||
Many properties, especially large numerical arrays, return a live view directly
|
||
into the WebAssembly memory. This is highly efficient as it avoids copying large
|
||
amounts of data.
|
||
|
||
A key example is `MjData.qpos` (joint positions). When you get a reference to
|
||
this array, it points directly to the simulation's state data. Any changes in
|
||
the simulation (e.g., after a call to `mj_step`) will be immediately reflected
|
||
in this array.
|
||
|
||
```typescript
|
||
// `qpos` is a live view into the simulation state.
|
||
const qpos = data.qpos;
|
||
|
||
console.log(qpos[0]); // Print initial position
|
||
|
||
// Step the simulation
|
||
mujoco.mj_step(model, data);
|
||
|
||
// `qpos` is automatically updated.
|
||
console.log(qpos[0]); // Print new position
|
||
|
||
// Remember to delete all created objects when they are no longer needed.
|
||
data.delete();
|
||
```
|
||
|
||
### Data Layout: Row-Major Matrices
|
||
|
||
When a function from the MuJoCo C API returns a matrix (or needs a matrix as
|
||
input), these are represented in the JavaScript bindings as flat,
|
||
one-dimensional `TypedArray`'s. The elements are stored in row-major order.
|
||
|
||
For example, a 3x10 matrix will be returned as a flat array with 30 elements.
|
||
The first 10 elements represent the first row, the next 10 represent the second
|
||
row, and so on.
|
||
|
||
Example: Accessing an element at `(row, col)`
|
||
```typescript
|
||
// A 3x10 matrix stored as a flat array.
|
||
const matrix: Float64Array = ...;
|
||
const nRows = 3;
|
||
const nCols = 10;
|
||
|
||
// To access the element at row `i` and column `j`:
|
||
const element = matrix[i * nCols + j];
|
||
```
|
||
|
||
### Working with Out Parameters
|
||
|
||
Many functions in the MuJoCo C API use "out parameters" to return data. This
|
||
means instead of returning a value, they write the result into one of the
|
||
arguments passed to them by reference (using pointers). In our JavaScript
|
||
bindings, you'll need to handle these cases specifically.
|
||
|
||
There are two main scenarios you'll encounter:
|
||
|
||
#### 1. Array-like Out Parameters
|
||
|
||
When a function expects a pointer to a primitive type (like `mjtNum*` or `int*`)
|
||
to write an array of values, you need to pre-allocate memory for the result on
|
||
the JavaScript side. We provide helper classes for this: `mujoco.Uint8Buffer`,
|
||
`mujoco.DoubleBuffer`, `mujoco.FloatBuffer`, and `mujoco.IntBuffer`.
|
||
|
||
Here's how to use them:
|
||
|
||
1. Create a buffer: Instantiate the appropriate buffer class with an initial array of the correct size (e.g., an array of zeros).
|
||
2. Call the function: Pass the buffer instance to the function as the out parameter.
|
||
3. Access the result: Use the `.getView()` method on the buffer to get a `TypedArray` view of the data written by the C++ function.
|
||
4. Free the memory: When you are done with the buffer, you must call the `.delete()` method to free the underlying memory and prevent memory leaks.
|
||
|
||
Example: Rotating a vector
|
||
|
||
The function `mju_rotVecQuat` rotates a vector `vec` by a quaternion `quat` and
|
||
stores the result in the `res` out parameter.
|
||
|
||
```typescript
|
||
// Create a buffer to hold the 3D vector result.
|
||
const res = new mujoco.DoubleBuffer([0, 0, 0]);
|
||
|
||
const vec = [1, 0, 0];
|
||
const quat = [0.707, 0, 0, 0.707]; // 90-degree rotation around z-axis
|
||
|
||
try {
|
||
// Call the function with the buffer as the out parameter.
|
||
mujoco.mju_rotVecQuat(res, vec, quat);
|
||
|
||
// Get the result as a Float64Array.
|
||
const resultView = res.getView();
|
||
console.log(resultView); // Expected: approximately [0, 1, 0]
|
||
|
||
} finally {
|
||
// IMPORTANT: Free the memory allocated for the buffer.
|
||
res.delete();
|
||
}
|
||
```
|
||
|
||
#### 2. Struct Out Parameters (e.g., mjvCamera*, mjvScene*)
|
||
|
||
When a function modifies a struct passed by pointer, you should pass an instance
|
||
of the corresponding JavaScript wrapper class. The underlying C++ struct will be
|
||
modified in place.
|
||
|
||
Example: Updating a scene
|
||
|
||
The function `mjv_updateScene` populates an `mjvScene` object with information
|
||
from `mjModel` and `mjData`.
|
||
```typescript
|
||
// Create instances of the necessary structs.
|
||
const model = mujoco.MjModel.from_xml_string(xmlContent);
|
||
const data = new mujoco.MjData(model);
|
||
const scene = new mujoco.MjvScene(model, 1000);
|
||
const option = new mujoco.MjvOption();
|
||
const perturb = new mujoco.MjvPerturb();
|
||
const camera = new mujoco.MjvCamera();
|
||
|
||
// ... (step simulation, etc.)
|
||
|
||
// Update the scene. The 'scene' object is modified by the function.
|
||
mujoco.mjv_updateScene(
|
||
model,
|
||
data,
|
||
option,
|
||
perturb,
|
||
camera,
|
||
mujoco.mjtCatBit.mjCAT_ALL.value,
|
||
scene
|
||
);
|
||
|
||
console.log('Number of geoms in scene:', scene.ngeom);
|
||
|
||
// Remember to delete all created objects when they are no longer needed.
|
||
scene.delete();
|
||
camera.delete();
|
||
perturb.delete();
|
||
option.delete();
|
||
data.delete();
|
||
model.delete();
|
||
```
|
||
|
||
As with buffers, you are responsible for managing the memory of these struct
|
||
instances and must call `.delete()` on them when you are finished.
|
||
|
||
### Enums
|
||
Access via `.value`:
|
||
```javascript
|
||
mujoco.mjtDisableBit.mjDSBL_CLAMPCTRL.value
|
||
```
|
||
|
||
### Constants
|
||
Scalar constants can be accessed as properties:
|
||
|
||
```javascript
|
||
mujoco.mjNEQDATA
|
||
```
|
||
|
||
Non-scalar constants like `mjFRAMESTRING` are also accessed as properties, and
|
||
return JavaScript arrays:
|
||
|
||
```javascript
|
||
mujoco.mjFRAMESTRING
|
||
```
|
||
|
||
This will return a javascript array representation of the values in MuJoCo
|
||
`mjFRAMESTRING`.
|
||
|
||
> [!NOTE]
|
||
> You will notice constants like `mjFRAMESTRING` are typed as `any`. This is
|
||
> because they are bound using `emscripten::val::array()` in C++, and Embind
|
||
> maps `emscripten::val` to `any` in TypeScript definition files. While
|
||
> `EMSCRIPTEN_DECLARE_VAL_TYPE(StringArray)` could be used to define
|
||
> `StringArray` as an alias for `emscripten::val` and hint to Embind how to
|
||
> handle conversions in function signatures or when using `.as()` it does not
|
||
> change how `emscripten::constant` infers types for properties.
|
||
|
||
## Development
|
||
|
||
In order to change the bindings you will need to change the [`bindings.cc`](codegen/generated/bindings.cc)
|
||
file but this should not be done manually. The file is generated using the
|
||
Python scripts and template files in the [`codegen`](codegen) folder, to edit
|
||
the bindings you will need to change those files and re-generate [`bindings.cc`](codegen/generated/bindings.cc)
|
||
using this command:
|
||
|
||
```sh
|
||
PYTHONPATH=python/mujoco python3 -m wasm.codegen.update
|
||
```
|
||
|
||
The codegen scripts use MuJoCo’s Python introspect library to generate the
|
||
Embind `EMSCRIPTEN_BINDINGS` block that binds C++ functions and classes to
|
||
JavaScript. The functions and classes that are bound are wrappers around
|
||
MuJoCo's C API. These wrappers provide a convenient place to add features like
|
||
bounds checking and nice error reporting.
|
||
|
||
### Testing
|
||
|
||
1. **JavaScript API tests.**
|
||
These verify that a wide variety of MuJoCo functions and classes work
|
||
correctly when called from JavaScript. Run the tests as follows:
|
||
|
||
```sh
|
||
npm run test --prefix ./wasm
|
||
```
|
||
|
||
2. **JavaScript API benchmark tests.**
|
||
The current benchmark tests check JavaScript/C++ shared memory buffers
|
||
performance. We will increase the coverage of the benchmarks overtime. Run
|
||
the benchmarks using this command:
|
||
|
||
```sh
|
||
npm run benchmark --prefix ./wasm
|
||
```
|
||
|
||
3. **Bindings generator tests.**
|
||
These are relevant when developing or extending the bindings. The following
|
||
command finds and runs all `test_*.py` or `*_test.py` files in the `wasm`
|
||
folder:
|
||
|
||
```sh
|
||
PYTHONPATH=python/mujoco python3 -m pytest ./wasm
|
||
```
|
||
|
||
### Debugging
|
||
|
||
We provide a “sandbox” app where you can quickly write code to run in your
|
||
browser. Write your code in the [`main.ts`](tests/sandbox/main.ts) file and use
|
||
the following command to execute it in your browser:
|
||
|
||
```sh
|
||
npm run dev:sandbox --prefix ./wasm
|
||
```
|
||
|
||
The page will be blank since the script only logs to the console output. You
|
||
can add your code at the indicated placeholder and use Chrome DevTools for
|
||
debugging. It is possible to set up a debug workflow where stack traces and
|
||
stepping through code across language boundaries work correctly. Our current
|
||
method to do this only works internally at Google, but it should be possible to
|
||
replicate the experience with open-source tooling — community suggestions are
|
||
welcome!
|
||
|
||
## Versioning
|
||
Package versions follow the official MuJoCo release versions.
|
||
For example:
|
||
|
||
| npm version | MuJoCo version |
|
||
|-------------|----------------|
|
||
| 3.5.0 | 3.5.0 |
|
||
|
||
## Future Work
|
||
|
||
1. **Bind all useful APIs.**
|
||
These bindings are not yet complete. While the main MuJoCo APIs (`mj_step`,
|
||
`mj_loadXML`, etc.) are well tested, other APIs (e.g., functions from
|
||
`mjspec.h`) remain untested in real web applications (though test code for
|
||
the `mjspec` bindings does exist).
|
||
|
||
2. **Improve the developer experience.**
|
||
There is still work to be done to improve the developer experience when
|
||
developing the WASM bindings. The most obvious issue is that bindings
|
||
generation is not yet fully automated. As a result, it is currently less
|
||
convenient than we'd like to identify and apply the changes needed to update
|
||
the bindings. The goal is to eventually automate all binding code generation
|
||
and clearly communicate what changes are required in the WASM bindings as a
|
||
result of C++ updates. This problem should only affect developers working on
|
||
the MuJoCo engine in C++, not end users writing JavaScript.
|
||
|
||
3. **Improve the documentation.**
|
||
The documentation in this README will eventually be merged into the main
|
||
MuJoCo documentation once the bindings are complete and named access is
|
||
implemented. We also intend to review the bindings APIs and make adjustments
|
||
to minimize differences with the Python bindings (while respecting language
|
||
idioms) to reduce the amount of additional documentation required.
|
||
|
||
4. **Improve the [example](#example-application).**
|
||
We aim to provide an example application that can be easily modified and
|
||
embedded into a paper project page (see [this example](https://kzakka.com/robopianist/)).
|
||
This could be achieved by extending the Three.js example or by compiling the
|
||
MuJoCo platform C++ code using the Emscripten toolchain. Community
|
||
suggestions and contributions are welcome!
|