mujoco-react 11.0.0 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,26 +10,25 @@ Composable [React Three Fiber](https://docs.pmnd.rs/react-three-fiber) wrapper a
10
10
 
11
11
  **[Demo](https://mujoco-react-example.pages.dev)** | **[Docs](https://dadd.mintlify.app)** | **[npm](https://www.npmjs.com/package/mujoco-react)** | **[Example Source](https://github.com/noah-wardlow/mujoco-react-example)** | **[llms.txt](https://dadd.mintlify.app/llms.txt)**
12
12
 
13
- ## MuJoCo 3.15 and analytic IK
14
-
15
- The engine dependency is pinned to `@mujoco/mujoco@3.15.0` so upstream minor releases
16
- cannot silently change model behavior.
17
-
18
- - `GenericIK` uses `mj_jacSite`; `solveJoints()` supports ball/free joints through `mj_integratePos` (see [IK docs](docs/api/ik-control.mdx)). Existing scalar solve calls remain supported; `epsilon` is ignored.
19
- - Actuator IDs and control addresses are distinct. `useActuators()` / `getActuators()` expose `ctrlAdr`, `ctrlCount`, and per-input `ranges`. Use `api.setCtrl('pid_servo', [position, velocity, feedforward])` or `useCtrl(name, inputIndex)` for multi-input actuators. Scalar hooks/groups reject ambiguous multi-input commands; low-level policy vectors remain flat control vectors.
20
- - `<MujocoProvider threadCount={2} ...>` initializes an engine thread pool for each simulation when using the opt-in threaded build. This parallelizes engine work; the main `mj_step` call still runs on the UI thread.
21
- - `<MujocoCanvas integrator="discrete">` and `<MujocoPhysics integrator="discrete">` support the new integrator without changing MJCF defaults when omitted.
22
- - Snapshots include MuJoCo integration state, including mocap, history, applied forces, and warmstart. They belong to the loaded model; save again after a scene reload. Legacy snapshots remain accepted. Experimental IPC flex contact is not exactly replayable because MuJoCo does not expose all of its state.
23
- - `FlexRenderer` draws indexed 2D surfaces and 3D boundary surfaces, plus 1D lines.
24
-
25
- Review [upstream migration notes](https://mujoco.readthedocs.io/en/3.15.0/changelog.html): actuator damping and flex integration have changed since 3.9. Scalar `data.ctrl[actuator.id]` consumers should use `data.ctrl[actuator.ctrlAdr]`; multi-input controls occupy `ctrlCount` consecutive entries. `homeJoints` is limited to scalar-input models; use MJCF initial poses/keyframes and explicit controls for multi-input models.
26
-
27
13
  ## Install
28
14
 
29
15
  ```bash
30
16
  npm install mujoco-react three @react-three/fiber @react-three/drei
31
17
  ```
32
18
 
19
+ ## Running policies and simulation workloads
20
+
21
+ Use `const runtime = await api.createRuntime()` after `onReady`, then await
22
+ `runtime.observe()` and `runtime.advance({ steps, controls })`. Physics runs in a
23
+ dedicated worker; commands reject when busy instead of accumulating. The runtime
24
+ supports cancellation, reset, full snapshots, synchronized camera observations,
25
+ and separate physics/capture timing. Use `<CameraView>` for GPU-only previews.
26
+
27
+ The viewer has an 8 ms / 100-step per-frame budget. Equivalent inline scene
28
+ configs preserve the loaded model. See the [simulation runtime guide](https://dadd.mintlify.app/guides/simulation-runtime)
29
+ for ownership, VLA loops, bundling, and performance limits, or try the
30
+ [worker demo](https://mujoco-react-example.pages.dev/?demo=runtime).
31
+
33
32
  ## Vite Plugin and Type-Safe Names
34
33
 
35
34
  Use the Vite plugin to generate declaration merging for actuator, sensor, body, joint, site, geom, and keyframe names:
@@ -50,6 +49,10 @@ export default defineConfig({
50
49
  });
51
50
  ```
52
51
 
52
+ The plugin also preserves worker asset URLs in Vite development by excluding
53
+ `mujoco-react` from dependency prebundling. Without it, set
54
+ `optimizeDeps: { exclude: ["mujoco-react"] }` when using the worker runtime.
55
+
53
56
  The plugin writes `src/mujoco-register.gen.ts` during dev and build. Commit that generated file. Vite auto-loads it, so app code imports generated values from `mujoco-react`, not from the generated file:
54
57
 
55
58
  ```ts
@@ -1167,8 +1170,8 @@ const frame = await apiRef.current?.captureCameraFrame({
1167
1170
  ```
1168
1171
 
1169
1172
  **Live streams.** `useCameraStream(canvasRef, options)` renders a camera into a
1170
- DOM `<canvas>` every frame (offscreen render → blit: no PNG round-trip, no render-
1171
- loop takeover, splat scenes stream safely). Call it inside `<MujocoCanvas>`; the
1173
+ DOM `<canvas>` at 10 FPS by default (offscreen render → GPU readback → blit).
1174
+ It avoids PNG encoding but still pays readback and pixel-conversion costs. Call it inside `<MujocoCanvas>`; the
1172
1175
  canvas can live anywhere in your DOM:
1173
1176
 
1174
1177
  ```tsx
@@ -1181,8 +1184,8 @@ function WristStream({ canvasRef }) {
1181
1184
  For a transparent picture-in-picture overlay, use `<CameraView>` (or
1182
1185
  `useCameraViewport` to track your own element). It scissors into the main canvas —
1183
1186
  cheaper, but it takes over the render loop while mounted (incompatible with
1184
- postprocessing, occluded by opaque DOM), so prefer `useCameraStream` for panel
1185
- tiles:
1187
+ postprocessing, occluded by opaque DOM), so use `useCameraStream` only when HTML compositing requires a separate canvas.
1188
+ Prefer GPU viewports for live simulation previews:
1186
1189
 
1187
1190
  ```tsx
1188
1191
  <MujocoCanvas config={config}>
@@ -1427,3 +1430,7 @@ These MuJoCo features are not yet exposed in the WASM binding:
1427
1430
  ## License
1428
1431
 
1429
1432
  Apache-2.0
1433
+
1434
+ ## Contacts and plasticity
1435
+
1436
+ Use MJCF speculative contacts for fast rigid objects and yielding springs for permanent deformation. The existing model/data interface exposes enable flags and plastic state; full snapshots preserve plastic history. See the [guide](docs/guides/engine-features.mdx) and [interactive demos](https://mujoco-react-example.pages.dev/?demo=speculative).
@@ -0,0 +1,137 @@
1
+ // src/runtime/engine.ts
2
+ var displayFields = [
3
+ "qpos",
4
+ "qvel",
5
+ "ctrl",
6
+ "act",
7
+ "plastic",
8
+ "sensordata",
9
+ "xpos",
10
+ "xquat",
11
+ "xmat",
12
+ "geom_xpos",
13
+ "geom_xmat",
14
+ "site_xpos",
15
+ "site_xmat",
16
+ "cam_xpos",
17
+ "cam_xmat",
18
+ "flexvert_xpos",
19
+ "ten_length",
20
+ "wrap_xpos",
21
+ "cvel",
22
+ "cfrc_ext",
23
+ "qfrc_applied",
24
+ "xfrc_applied",
25
+ "qfrc_bias",
26
+ "ten_wrapadr",
27
+ "ten_wrapnum"
28
+ ];
29
+ var SimulationEngine = class {
30
+ constructor(mj, initial) {
31
+ this.mj = mj;
32
+ this.initial = initial;
33
+ mj.FS.writeFile("/runtime.mjb", initial.model);
34
+ const vfs = new mj.MjVFS();
35
+ try {
36
+ this.model = mj.MjModel.from_binary_path("/runtime.mjb", vfs);
37
+ } finally {
38
+ vfs.delete();
39
+ mj.FS.unlink("/runtime.mjb");
40
+ }
41
+ this.data = new mj.MjData(this.model);
42
+ try {
43
+ this.reset();
44
+ } catch (error) {
45
+ this.dispose();
46
+ throw error;
47
+ }
48
+ }
49
+ model;
50
+ data;
51
+ reset() {
52
+ this.mj.mj_resetData(this.model, this.data);
53
+ this.restore(this.initial.state);
54
+ }
55
+ restore(state) {
56
+ if (state.signature !== this.mj.mjtState.mjSTATE_INTEGRATION.value || state.values.length !== this.mj.mj_stateSize(this.model, state.signature) || !state.values.every(Number.isFinite)) throw new Error("Invalid runtime snapshot");
57
+ this.mj.mj_setState(this.model, this.data, Array.from(state.values), state.signature);
58
+ this.mj.mj_forward(this.model, this.data);
59
+ }
60
+ setControls(controls) {
61
+ if (controls) {
62
+ if (controls.length !== this.data.ctrl.length || !controls.every(Number.isFinite)) throw new Error("Invalid runtime controls");
63
+ this.data.ctrl.set(controls);
64
+ }
65
+ }
66
+ step() {
67
+ const expected = this.data.time + this.model.opt.timestep;
68
+ this.mj.mj_step(this.model, this.data);
69
+ if (Math.abs(this.data.time - expected) > 1e-7 || !this.data.qpos.every(Number.isFinite)) {
70
+ throw new Error("Physics became unstable or reset unexpectedly");
71
+ }
72
+ }
73
+ save() {
74
+ const signature = this.mj.mjtState.mjSTATE_INTEGRATION.value;
75
+ const buffer = new this.mj.DoubleBuffer(this.mj.mj_stateSize(this.model, signature));
76
+ try {
77
+ this.mj.mj_getState(this.model, this.data, buffer, signature);
78
+ return { signature, values: new Float64Array(buffer.GetView()) };
79
+ } finally {
80
+ buffer.delete();
81
+ }
82
+ }
83
+ snapshot(complete = true) {
84
+ const integration = complete ? this.save() : void 0;
85
+ if (integration) this.mj.mj_forward(this.model, this.data);
86
+ try {
87
+ return {
88
+ time: this.data.time,
89
+ fields: Object.fromEntries(displayFields.map((key) => [key, new Float64Array(this.data[key])])),
90
+ contacts: complete ? this.contacts() : [],
91
+ ...integration ? { integration } : {}
92
+ };
93
+ } finally {
94
+ if (integration) this.mj.mj_setState(this.model, this.data, Array.from(integration.values), integration.signature);
95
+ }
96
+ }
97
+ contacts() {
98
+ const result = [], vector = this.data.contact;
99
+ const names = this.model.names, addresses = this.model.name_geomadr;
100
+ const name = (id) => {
101
+ if (id < 0) return "flex";
102
+ const start = addresses[id];
103
+ let end = start;
104
+ while (end < names.length && names[end]) end++;
105
+ return new TextDecoder().decode(names.subarray(start, end));
106
+ };
107
+ try {
108
+ for (let i = 0; i < this.data.ncon; i++) {
109
+ const c = vector.get(i);
110
+ if (!c) continue;
111
+ try {
112
+ result.push({
113
+ geom1: c.geom1,
114
+ geom2: c.geom2,
115
+ geom1Name: name(c.geom1),
116
+ geom2Name: name(c.geom2),
117
+ pos: Array.from(c.pos),
118
+ depth: c.dist
119
+ });
120
+ } finally {
121
+ c.delete();
122
+ }
123
+ }
124
+ } finally {
125
+ vector.delete();
126
+ }
127
+ return result;
128
+ }
129
+ dispose() {
130
+ this.data.delete();
131
+ this.model.delete();
132
+ }
133
+ };
134
+
135
+ export { SimulationEngine, displayFields };
136
+ //# sourceMappingURL=chunk-U2Y2BODT.js.map
137
+ //# sourceMappingURL=chunk-U2Y2BODT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime/engine.ts"],"names":[],"mappings":"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","file":"chunk-U2Y2BODT.js","sourcesContent":["import type { MainModule, MjModel, MjData } from '@mujoco/mujoco';\nimport type { ContactInfo } from '../types';\n\nexport const displayFields = ['qpos', 'qvel', 'ctrl', 'act', 'plastic', 'sensordata', 'xpos', 'xquat', 'xmat',\n 'geom_xpos', 'geom_xmat', 'site_xpos', 'site_xmat', 'cam_xpos', 'cam_xmat',\n 'flexvert_xpos', 'ten_length', 'wrap_xpos', 'cvel', 'cfrc_ext', 'qfrc_applied', 'xfrc_applied', 'qfrc_bias', 'ten_wrapadr', 'ten_wrapnum'] as const;\nexport interface EngineState { signature: number; values: Float64Array }\nexport interface EngineInitialization { model: Uint8Array; state: EngineState }\nexport interface DisplayState {\n time: number;\n fields: Record<typeof displayFields[number], Float64Array>;\n contacts: ContactInfo[];\n integration?: EngineState;\n}\nexport class SimulationEngine {\n readonly model: MjModel;\n readonly data: MjData;\n constructor(private mj: MainModule, private initial: EngineInitialization) {\n mj.FS.writeFile('/runtime.mjb', initial.model);\n const vfs = new mj.MjVFS();\n try { this.model = mj.MjModel.from_binary_path('/runtime.mjb', vfs); }\n finally { vfs.delete(); mj.FS.unlink('/runtime.mjb'); }\n this.data = new mj.MjData(this.model);\n try { this.reset(); } catch (error) { this.dispose(); throw error; }\n }\n reset() {\n this.mj.mj_resetData(this.model, this.data);\n this.restore(this.initial.state);\n }\n restore(state: EngineState) {\n if (state.signature !== this.mj.mjtState.mjSTATE_INTEGRATION.value ||\n state.values.length !== this.mj.mj_stateSize(this.model, state.signature) ||\n !state.values.every(Number.isFinite)) throw new Error('Invalid runtime snapshot');\n this.mj.mj_setState(this.model, this.data, Array.from(state.values), state.signature);\n this.mj.mj_forward(this.model, this.data);\n }\n setControls(controls?: Float64Array) {\n if (controls) {\n if (controls.length !== this.data.ctrl.length || !controls.every(Number.isFinite)) throw new Error('Invalid runtime controls');\n this.data.ctrl.set(controls);\n }\n }\n step() {\n const expected = this.data.time + this.model.opt.timestep;\n this.mj.mj_step(this.model, this.data);\n if (Math.abs(this.data.time - expected) > 1e-7 || !this.data.qpos.every(Number.isFinite)) {\n throw new Error('Physics became unstable or reset unexpectedly');\n }\n }\n save(): EngineState {\n const signature = this.mj.mjtState.mjSTATE_INTEGRATION.value;\n const buffer = new this.mj.DoubleBuffer(this.mj.mj_stateSize(this.model, signature));\n try {\n this.mj.mj_getState(this.model, this.data, buffer, signature);\n return { signature, values: new Float64Array(buffer.GetView()) };\n } finally { buffer.delete(); }\n }\n snapshot(complete = true): DisplayState {\n const integration = complete ? this.save() : undefined;\n // mj_step integrates qpos after computing derived fields. Refresh completed\n // observations at their reported time, preserving solver warmstart exactly.\n if (integration) this.mj.mj_forward(this.model, this.data);\n try { return {\n time: this.data.time,\n fields: Object.fromEntries(displayFields.map(key => [key, new Float64Array(this.data[key])])) as DisplayState['fields'],\n contacts: complete ? this.contacts() : [],\n ...(integration ? { integration } : {}),\n }; } finally {\n if (integration) this.mj.mj_setState(this.model, this.data, Array.from(integration.values), integration.signature);\n }\n }\n private contacts(): ContactInfo[] {\n const result: ContactInfo[] = [], vector = this.data.contact;\n const names = this.model.names, addresses = this.model.name_geomadr;\n const name = (id: number) => {\n if (id < 0) return 'flex';\n const start = addresses[id]; let end = start;\n while (end < names.length && names[end]) end++;\n return new TextDecoder().decode(names.subarray(start, end));\n };\n try {\n for (let i = 0; i < this.data.ncon; i++) {\n const c = vector.get(i);\n if (!c) continue;\n try { result.push({ geom1: c.geom1, geom2: c.geom2, geom1Name: name(c.geom1), geom2Name: name(c.geom2),\n pos: Array.from(c.pos) as [number, number, number], depth: c.dist }); }\n finally { c.delete(); }\n }\n } finally { vector.delete(); }\n return result;\n }\n dispose() { this.data.delete(); this.model.delete(); }\n}\n"]}
@@ -315,8 +315,8 @@ function prepareCaptureCamera(camera, options, fallbackCamera, width, height) {
315
315
  applyProjectionMatrix(camera, options.projectionMatrix);
316
316
  applyCameraPose(camera, options, fallbackCamera);
317
317
  }
318
- function readRenderTargetToCanvas(renderer, target, canvas, context, pixels, imageData, width, height, outputColorSpace, flipX = false) {
319
- renderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
318
+ function readRenderTargetToCanvas(renderer, target, canvas, context, pixels, imageData, width, height, outputColorSpace, flipX = false, readback = true) {
319
+ if (readback) renderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
320
320
  const rowBytes = width * 4;
321
321
  const encodeSrgb = outputColorSpace === THREE.SRGBColorSpace;
322
322
  for (let y = 0; y < height; y += 1) {
@@ -532,6 +532,15 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
532
532
  const drawContext = context;
533
533
  const pixels = new Uint8Array(width * height * 4);
534
534
  const imageData = drawContext.createImageData(width, height);
535
+ let inFlight = false, disposed = false;
536
+ function assertAvailable() {
537
+ if (disposed) throw new Error("Camera capture session is disposed");
538
+ if (inFlight) throw new Error("Camera capture session is busy; await the current capture");
539
+ }
540
+ function releaseResources() {
541
+ target.dispose();
542
+ if (isolatedRenderer && !isolatedRenderer.cached) isolatedRenderer.renderer.dispose();
543
+ }
535
544
  function resolveCaptureOptions(nextOptions = {}) {
536
545
  const captureOptions = { ...options, ...nextOptions };
537
546
  if (shouldUseRenderIsolation(captureOptions) !== shouldUseRenderIsolation(options)) {
@@ -614,9 +623,11 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
614
623
  };
615
624
  }
616
625
  function capture(nextOptions = {}) {
626
+ assertAvailable();
617
627
  return renderPreparedCapture(resolveCaptureOptions(nextOptions));
618
628
  }
619
629
  function capturePixels(nextOptions = {}) {
630
+ assertAvailable();
620
631
  const captureOptions = resolveCaptureOptions(nextOptions);
621
632
  renderCaptureToTarget(captureOptions, () => {
622
633
  sessionRenderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
@@ -643,6 +654,16 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
643
654
  return { ...tensor, camera, source: result.source };
644
655
  }
645
656
  async function captureAsync(nextOptions = {}) {
657
+ assertAvailable();
658
+ inFlight = true;
659
+ try {
660
+ return await captureAsyncInternal(nextOptions);
661
+ } finally {
662
+ inFlight = false;
663
+ if (disposed) releaseResources();
664
+ }
665
+ }
666
+ async function captureAsyncInternal(nextOptions = {}) {
646
667
  const captureOptions = resolveCaptureOptions(nextOptions);
647
668
  runCapturePreRenderHooks(scene);
648
669
  scene.updateMatrixWorld(true);
@@ -707,7 +728,25 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
707
728
  restoreRendererState(sessionRenderer, previousState);
708
729
  }
709
730
  }
710
- return renderPreparedCapture(captureOptions);
731
+ let readback;
732
+ renderCaptureToTarget(captureOptions, () => {
733
+ readback = sessionRenderer.readRenderTargetPixelsAsync(target, 0, 0, width, height, pixels);
734
+ });
735
+ await readback;
736
+ readRenderTargetToCanvas(
737
+ sessionRenderer,
738
+ target,
739
+ canvas,
740
+ drawContext,
741
+ pixels,
742
+ imageData,
743
+ width,
744
+ height,
745
+ sessionRenderer.outputColorSpace,
746
+ captureOptions.flipX ?? false,
747
+ false
748
+ );
749
+ return { canvas, camera, width, height, source: getCameraFrameCaptureSource(captureOptions) };
711
750
  }
712
751
  return {
713
752
  width,
@@ -756,10 +795,9 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
756
795
  return { ...result, blob, type };
757
796
  },
758
797
  dispose() {
759
- target.dispose();
760
- if (isolatedRenderer && !isolatedRenderer.cached) {
761
- isolatedRenderer.renderer.dispose();
762
- }
798
+ if (disposed) return;
799
+ disposed = true;
800
+ if (!inFlight) releaseResources();
763
801
  }
764
802
  };
765
803
  }
@@ -1433,5 +1471,5 @@ function clamp01(value) {
1433
1471
  */
1434
1472
 
1435
1473
  export { CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, captureCameraFrameTensor, createCameraFrameCaptureSession, createCaptureCamera, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, dataUrlToPolicyImageTensor, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, imageDataToPolicyImageTensor, pixelsToPolicyImageTensor, prepareCaptureCamera, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
1436
- //# sourceMappingURL=chunk-UCQ5MJP4.js.map
1437
- //# sourceMappingURL=chunk-UCQ5MJP4.js.map
1474
+ //# sourceMappingURL=chunk-XD3AY7XZ.js.map
1475
+ //# sourceMappingURL=chunk-XD3AY7XZ.js.map