7.5 KiB
MuJoCo WASM bindings
Official WebAssembly (WASM) bindings for the MuJoCo physics engine, compiled from the MuJoCo C/C++ sources into WASM with JS glue (Emscripten + Embind). The package ships prebuilt ESM-ready JS/WASM artifacts and TypeScript types for immediate use.
Important:These bindings are still a WIP.
Install
npm install mujoco_wasm
Quick start (ESM / TypeScript)
import loadMujoco from 'mujoco_wasm';
const mujoco = await loadMujoco();
const model = mujoco.MjModel.fromXMLString(`
<mujoco>
<worldbody>
<geom type="sphere" size="0.1"/>
</worldbody>
</mujoco>
`);
const data = new mujoco.MjData(model);
mujoco.mj_step(model, data);
Usage Guide
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.
Copy vs. 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 a new array containing the contacts at that specific moment in the simulation. If you step the simulation forward, this array will not be updated. You must access data.contact again to get the new contact information.
Example:
// 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;
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.
// `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
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)
// 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:
- Create a buffer: Instantiate the appropriate buffer class with an initial array of the correct size (e.g., an array of zeros).
- Call the function: Pass the buffer instance to the function as the out parameter.
- Access the result: Use the
.getView()method on the buffer to get aTypedArrayview of the data written by the C++ function. - 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.
// 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.
// Create instances of the necessary structs.
const model = mujoco.MjModel.loadFromXML(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:
mujoco.mjtDisableBit.mjDSBL_CLAMPCTRL.value
Constants
Scalar constants will be accessed the same way they are on python, simply:
mujoco.mjNEQDATA
Due to Embind limitations, more complex constants that are not scalar, but are represented in more dimensions are exposed as functions. E.g to use mujoco.mjFRAMESTRING you will need to call a function:
mujoco.get_mjFRAMESTRING()
This will return a javascript array representation of the values in MuJoCo mjFRAMESTRING.
Notes
The package is ESM (type: module) and ships TypeScript types.
Ensure your bundler or dev server serves the .wasm asset from
node_modules/mujoco_wasm/ at runtime.
Development
For detailed build instructions see the repository README (Emscripten toolchain, Embind bindings, producing the .wasm artifact, and targets for Node and browser).
Versioning
Package versions follow the official MuJoCo release versions. For example:
| npm version | MuJoCo version |
|---|---|
| 3.5.0 | 3.5.0 |
For full engine documentation, see: https://mujoco.readthedocs.io