mujoco-react 10.2.1 → 10.4.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
@@ -748,6 +748,7 @@ Thin wrapper around R3F `<Canvas>`. Accepts all R3F Canvas props plus:
748
748
  | `substeps` | `number` | mj_step calls per frame |
749
749
  | `paused` | `boolean` | Declarative pause |
750
750
  | `speed` | `number` | Simulation speed multiplier |
751
+ | `renderOptions` | `MujocoRenderOptions` | Optional render-time geometry settings such as `meshNormalSmoothing` |
751
752
 
752
753
  ### `<MujocoPhysics>`
753
754
 
@@ -859,6 +860,7 @@ Visualization overlays:
859
860
  | `showJoints` | `boolean?` | `false` | Joint axes |
860
861
  | `showContacts` | `boolean?` | `false` | Contact force vectors |
861
862
  | `showCameras` | `boolean?` | `false` | MuJoCo camera positions, frustums, and forward rays |
863
+ | `virtualCameras` | `DebugVirtualCamera[]?` | `[]` | Explicit virtual policy/offscreen render camera poses to draw alongside MuJoCo cameras |
862
864
  | `showCOM` | `boolean?` | `false` | Center of mass markers |
863
865
  | `showInertia` | `boolean?` | `false` | Inertia ellipsoids |
864
866
  | `showTendons` | `boolean?` | `false` | Tendon paths |
@@ -867,7 +869,7 @@ Visualization overlays:
867
869
  | `contactColor` | `string?` | `"#ff4444"` | Color for contact force arrows |
868
870
  | `comColor` | `string?` | `"#ff0000"` | Color for COM markers |
869
871
 
870
- Camera debug overlays use the live MuJoCo `cam_xpos` / `cam_xmat` frame, so the frustum matches mounted camera captures and follows parent body motion.
872
+ Camera debug overlays use the live MuJoCo `cam_xpos` / `cam_xmat` frame, so the frustum matches mounted camera captures and follows parent body motion. Use `virtualCameras` for synthetic fixed policy/offscreen render viewpoints that are not declared as MJCF `<camera>` elements. Debug camera overlays are excluded from camera captures.
871
873
 
872
874
  ### `<TendonRenderer />`
873
875
 
@@ -1112,6 +1114,26 @@ needs an offscreen camera render at a stable resolution without moving the
1112
1114
  user's interactive viewport. Pass `cameraName`, `siteName`, or `bodyName` to
1113
1115
  record true MuJoCo-mounted camera frames; the returned image includes
1114
1116
  `source.kind` so dataset pipelines can reject fallback or synthetic fixed poses.
1117
+ For named MuJoCo cameras, set `mujocoCameraCompatibility` when you want the
1118
+ Three.js offscreen camera to inherit the MJCF camera's `resolution`, `fovy`,
1119
+ near/far clipping, and calibrated intrinsics when the WASM model exposes
1120
+ `cam_intrinsic` plus `cam_sensorsize`:
1121
+
1122
+ ```tsx
1123
+ const frame = await apiRef.current?.captureCameraFrame({
1124
+ cameraName: "front_camera",
1125
+ width: 640,
1126
+ type: "image/jpeg",
1127
+ mujocoCameraCompatibility: true,
1128
+ });
1129
+ ```
1130
+
1131
+ This is still rendered by Three.js. It is useful for browser policy payloads and
1132
+ dataset debugging until you have native MuJoCo framebuffer bindings available.
1133
+ For canonical policy/training captures, use `visualOverrides` to temporarily
1134
+ override scene background, environment, fog, shadow maps, tone mapping, or color
1135
+ space, and `renderIsolation` to render with an independent offscreen
1136
+ `WebGLRenderer` instead of inheriting viewer renderer state.
1115
1137
 
1116
1138
  Use `recordCameraSequence()` / `useCameraSequenceRecorder()` to step policy
1117
1139
  rollouts and capture synchronized per-camera frames from one or more MuJoCo
@@ -88,6 +88,72 @@ var SplatEnvironmentReadinessStatus = {
88
88
  var CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY = "mujocoReactCameraFrameCaptureRender";
89
89
  var CAMERA_FRAME_CAPTURE_PRE_RENDER_USER_DATA_KEY = "mujocoReactCameraFrameCapturePreRender";
90
90
  var CAPTURE_EXCLUDE_KEY = "mujoco.capture.exclude";
91
+ var isolatedRendererCache = /* @__PURE__ */ new WeakMap();
92
+ function shouldUseRenderIsolation(options) {
93
+ return options.renderIsolation === true || typeof options.renderIsolation === "object" && options.renderIsolation.enabled !== false;
94
+ }
95
+ function getRenderIsolationOptions(options) {
96
+ return typeof options.renderIsolation === "object" ? options.renderIsolation : {};
97
+ }
98
+ function getRenderIsolationCacheKey(width, height, options) {
99
+ const isolation = getRenderIsolationOptions(options);
100
+ return JSON.stringify({
101
+ width,
102
+ height,
103
+ antialias: isolation.antialias ?? false,
104
+ alpha: isolation.alpha ?? false,
105
+ preserveDrawingBuffer: isolation.preserveDrawingBuffer ?? false,
106
+ powerPreference: isolation.powerPreference ?? null
107
+ });
108
+ }
109
+ function createIsolatedRenderer(sourceRenderer, width, height, options) {
110
+ if (!shouldUseRenderIsolation(options)) return null;
111
+ const isolation = getRenderIsolationOptions(options);
112
+ if (isolation.cache !== false) {
113
+ const cacheKey = getRenderIsolationCacheKey(width, height, options);
114
+ let rendererCache = isolatedRendererCache.get(sourceRenderer);
115
+ if (!rendererCache) {
116
+ rendererCache = /* @__PURE__ */ new Map();
117
+ isolatedRendererCache.set(sourceRenderer, rendererCache);
118
+ }
119
+ const cachedRenderer = rendererCache.get(cacheKey);
120
+ if (cachedRenderer) {
121
+ cachedRenderer.outputColorSpace = sourceRenderer.outputColorSpace;
122
+ cachedRenderer.toneMapping = sourceRenderer.toneMapping;
123
+ cachedRenderer.shadowMap.enabled = false;
124
+ return { renderer: cachedRenderer, cached: true };
125
+ }
126
+ const createdRenderer = createUncachedIsolatedRenderer(
127
+ sourceRenderer,
128
+ width,
129
+ height,
130
+ options
131
+ );
132
+ rendererCache.set(cacheKey, createdRenderer);
133
+ return { renderer: createdRenderer, cached: true };
134
+ }
135
+ return {
136
+ renderer: createUncachedIsolatedRenderer(sourceRenderer, width, height, options),
137
+ cached: false
138
+ };
139
+ }
140
+ function createUncachedIsolatedRenderer(sourceRenderer, width, height, options) {
141
+ const isolation = getRenderIsolationOptions(options);
142
+ const canvas = document.createElement("canvas");
143
+ const renderer = new THREE.WebGLRenderer({
144
+ canvas,
145
+ antialias: isolation.antialias ?? false,
146
+ alpha: isolation.alpha ?? false,
147
+ preserveDrawingBuffer: isolation.preserveDrawingBuffer ?? false,
148
+ powerPreference: isolation.powerPreference
149
+ });
150
+ renderer.setPixelRatio(1);
151
+ renderer.setSize(width, height, false);
152
+ renderer.outputColorSpace = sourceRenderer.outputColorSpace;
153
+ renderer.toneMapping = sourceRenderer.toneMapping;
154
+ renderer.shadowMap.enabled = false;
155
+ return renderer;
156
+ }
91
157
  function toVector3(value, fallback) {
92
158
  if (!value) return fallback.clone();
93
159
  return value instanceof THREE.Vector3 ? value.clone() : new THREE.Vector3(value[0], value[1], value[2]);
@@ -113,6 +179,15 @@ function applyCameraPose(camera, options, fallbackCamera) {
113
179
  }
114
180
  camera.updateMatrixWorld();
115
181
  }
182
+ function applyProjectionMatrix(camera, projectionMatrix) {
183
+ if (!projectionMatrix) return;
184
+ if (projectionMatrix instanceof THREE.Matrix4) {
185
+ camera.projectionMatrix.copy(projectionMatrix);
186
+ } else {
187
+ camera.projectionMatrix.fromArray(projectionMatrix);
188
+ }
189
+ camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
190
+ }
116
191
  function createCaptureCamera(options, fallbackCamera, width, height) {
117
192
  const camera = options.camera ? options.camera.clone() : fallbackCamera instanceof THREE.PerspectiveCamera ? fallbackCamera.clone() : new THREE.PerspectiveCamera(45, width / height, 0.01, 100);
118
193
  if (camera instanceof THREE.PerspectiveCamera) {
@@ -122,6 +197,7 @@ function createCaptureCamera(options, fallbackCamera, width, height) {
122
197
  camera.far = options.far ?? camera.far;
123
198
  camera.updateProjectionMatrix();
124
199
  }
200
+ applyProjectionMatrix(camera, options.projectionMatrix);
125
201
  applyCameraPose(camera, options, fallbackCamera);
126
202
  return camera;
127
203
  }
@@ -147,6 +223,7 @@ function prepareCaptureCamera(camera, options, fallbackCamera, width, height) {
147
223
  camera.far = options.far ?? camera.far;
148
224
  camera.updateProjectionMatrix();
149
225
  }
226
+ applyProjectionMatrix(camera, options.projectionMatrix);
150
227
  applyCameraPose(camera, options, fallbackCamera);
151
228
  }
152
229
  function readRenderTargetToCanvas(renderer, target, canvas, context, pixels, imageData, width, height, outputColorSpace, flipX = false) {
@@ -269,7 +346,10 @@ function saveRendererState(renderer) {
269
346
  scissorTest: renderer.getScissorTest(),
270
347
  clearColor,
271
348
  clearAlpha: renderer.getClearAlpha(),
272
- autoClear: renderer.autoClear
349
+ autoClear: renderer.autoClear,
350
+ shadowMapEnabled: renderer.shadowMap.enabled,
351
+ toneMapping: renderer.toneMapping,
352
+ outputColorSpace: renderer.outputColorSpace
273
353
  };
274
354
  }
275
355
  function restoreRendererState(renderer, state) {
@@ -280,6 +360,51 @@ function restoreRendererState(renderer, state) {
280
360
  renderer.setScissorTest(state.scissorTest);
281
361
  renderer.setClearColor(state.clearColor, state.clearAlpha);
282
362
  renderer.autoClear = state.autoClear;
363
+ renderer.shadowMap.enabled = state.shadowMapEnabled;
364
+ renderer.toneMapping = state.toneMapping;
365
+ renderer.outputColorSpace = state.outputColorSpace;
366
+ }
367
+ function saveSceneVisualState(scene) {
368
+ return {
369
+ background: scene.background,
370
+ environment: scene.environment,
371
+ fog: scene.fog
372
+ };
373
+ }
374
+ function restoreSceneVisualState(scene, state) {
375
+ scene.background = state.background;
376
+ scene.environment = state.environment;
377
+ scene.fog = state.fog;
378
+ }
379
+ function hasOwn(object, key) {
380
+ return Object.prototype.hasOwnProperty.call(object, key);
381
+ }
382
+ function applyCaptureVisualOverrides(renderer, scene, options) {
383
+ const overrides = options.visualOverrides;
384
+ if (!overrides) return null;
385
+ const previousSceneState = saveSceneVisualState(scene);
386
+ if (hasOwn(overrides, "sceneBackground")) {
387
+ const background = overrides.sceneBackground;
388
+ scene.background = background === false ? null : typeof background === "string" || typeof background === "number" ? new THREE.Color(background) : background ?? null;
389
+ }
390
+ if (hasOwn(overrides, "sceneEnvironment")) {
391
+ const environment = overrides.sceneEnvironment;
392
+ scene.environment = environment === false ? null : environment ?? null;
393
+ }
394
+ if (hasOwn(overrides, "sceneFog")) {
395
+ const fog = overrides.sceneFog;
396
+ scene.fog = fog === false ? null : fog ?? null;
397
+ }
398
+ if (overrides.shadows !== void 0) {
399
+ renderer.shadowMap.enabled = overrides.shadows;
400
+ }
401
+ if (overrides.toneMapping !== void 0) {
402
+ renderer.toneMapping = overrides.toneMapping;
403
+ }
404
+ if (overrides.outputColorSpace !== void 0) {
405
+ renderer.outputColorSpace = overrides.outputColorSpace;
406
+ }
407
+ return previousSceneState;
283
408
  }
284
409
  function getCaptureRenderer(scene) {
285
410
  const renderers = [];
@@ -300,6 +425,8 @@ function runCapturePreRenderHooks(scene) {
300
425
  }
301
426
  function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, options = {}) {
302
427
  const { width, height } = getCaptureDimensions(renderer, options);
428
+ const isolatedRenderer = createIsolatedRenderer(renderer, width, height, options);
429
+ const sessionRenderer = isolatedRenderer?.renderer ?? renderer;
303
430
  const camera = createCaptureCamera(options, fallbackCamera, width, height);
304
431
  const target = new THREE.WebGLRenderTarget(width, height, {
305
432
  format: THREE.RGBAFormat,
@@ -318,6 +445,11 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
318
445
  const imageData = drawContext.createImageData(width, height);
319
446
  function resolveCaptureOptions(nextOptions = {}) {
320
447
  const captureOptions = { ...options, ...nextOptions };
448
+ if (shouldUseRenderIsolation(captureOptions) !== shouldUseRenderIsolation(options)) {
449
+ throw new Error(
450
+ "Camera frame capture sessions require stable renderIsolation settings."
451
+ );
452
+ }
321
453
  const nextDimensions = getCaptureDimensions(renderer, captureOptions);
322
454
  if (nextDimensions.width !== width || nextDimensions.height !== height) {
323
455
  throw new Error(
@@ -334,7 +466,12 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
334
466
  return captureOptions;
335
467
  }
336
468
  function renderPreparedCapture(captureOptions) {
337
- const previousState = saveRendererState(renderer);
469
+ const previousState = saveRendererState(sessionRenderer);
470
+ const previousSceneState = applyCaptureVisualOverrides(
471
+ sessionRenderer,
472
+ scene,
473
+ captureOptions
474
+ );
338
475
  const hidden = [
339
476
  ...hideExcludedCaptureObjects(scene),
340
477
  ...hideCaptureGeomGroups(scene, captureOptions)
@@ -342,23 +479,23 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
342
479
  runCapturePreRenderHooks(scene);
343
480
  scene.updateMatrixWorld(true);
344
481
  try {
345
- renderer.xr.enabled = false;
346
- renderer.setRenderTarget(target);
347
- renderer.setViewport(0, 0, width, height);
348
- renderer.setScissor(0, 0, width, height);
349
- renderer.setScissorTest(false);
482
+ sessionRenderer.xr.enabled = false;
483
+ sessionRenderer.setRenderTarget(target);
484
+ sessionRenderer.setViewport(0, 0, width, height);
485
+ sessionRenderer.setScissor(0, 0, width, height);
486
+ sessionRenderer.setScissorTest(false);
350
487
  if (captureOptions.background !== void 0) {
351
- renderer.setClearColor(
488
+ sessionRenderer.setClearColor(
352
489
  new THREE.Color(captureOptions.background),
353
490
  captureOptions.backgroundAlpha ?? previousState.clearAlpha
354
491
  );
355
492
  } else if (captureOptions.backgroundAlpha !== void 0) {
356
- renderer.setClearColor(previousState.clearColor, captureOptions.backgroundAlpha);
493
+ sessionRenderer.setClearColor(previousState.clearColor, captureOptions.backgroundAlpha);
357
494
  }
358
- renderer.clear();
359
- renderer.render(scene, camera);
495
+ sessionRenderer.clear();
496
+ sessionRenderer.render(scene, camera);
360
497
  readRenderTargetToCanvas(
361
- renderer,
498
+ sessionRenderer,
362
499
  target,
363
500
  canvas,
364
501
  drawContext,
@@ -366,7 +503,7 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
366
503
  imageData,
367
504
  width,
368
505
  height,
369
- renderer.outputColorSpace,
506
+ sessionRenderer.outputColorSpace,
370
507
  captureOptions.flipX ?? false
371
508
  );
372
509
  return {
@@ -378,7 +515,8 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
378
515
  };
379
516
  } finally {
380
517
  restoreObjectVisibility(hidden);
381
- restoreRendererState(renderer, previousState);
518
+ if (previousSceneState) restoreSceneVisualState(scene, previousSceneState);
519
+ restoreRendererState(sessionRenderer, previousState);
382
520
  }
383
521
  }
384
522
  function capture(nextOptions = {}) {
@@ -390,23 +528,28 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
390
528
  scene.updateMatrixWorld(true);
391
529
  const captureRenderer = getCaptureRenderer(scene);
392
530
  if (captureRenderer) {
393
- const previousState = saveRendererState(renderer);
531
+ const previousState = saveRendererState(sessionRenderer);
532
+ const previousSceneState = applyCaptureVisualOverrides(
533
+ sessionRenderer,
534
+ scene,
535
+ captureOptions
536
+ );
394
537
  const hidden = [
395
538
  ...hideExcludedCaptureObjects(scene),
396
539
  ...hideCaptureGeomGroups(scene, captureOptions)
397
540
  ];
398
541
  try {
399
- renderer.xr.enabled = false;
542
+ sessionRenderer.xr.enabled = false;
400
543
  if (captureOptions.background !== void 0) {
401
- renderer.setClearColor(
544
+ sessionRenderer.setClearColor(
402
545
  new THREE.Color(captureOptions.background),
403
546
  captureOptions.backgroundAlpha ?? previousState.clearAlpha
404
547
  );
405
548
  } else if (captureOptions.backgroundAlpha !== void 0) {
406
- renderer.setClearColor(previousState.clearColor, captureOptions.backgroundAlpha);
549
+ sessionRenderer.setClearColor(previousState.clearColor, captureOptions.backgroundAlpha);
407
550
  }
408
551
  const captureResult = await captureRenderer({
409
- renderer,
552
+ renderer: sessionRenderer,
410
553
  scene,
411
554
  camera,
412
555
  target,
@@ -440,7 +583,8 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
440
583
  }
441
584
  } finally {
442
585
  restoreObjectVisibility(hidden);
443
- restoreRendererState(renderer, previousState);
586
+ if (previousSceneState) restoreSceneVisualState(scene, previousSceneState);
587
+ restoreRendererState(sessionRenderer, previousState);
444
588
  }
445
589
  }
446
590
  return renderPreparedCapture(captureOptions);
@@ -491,6 +635,9 @@ function createCameraFrameCaptureSession(renderer, scene, fallbackCamera, option
491
635
  },
492
636
  dispose() {
493
637
  target.dispose();
638
+ if (isolatedRenderer && !isolatedRenderer.cached) {
639
+ isolatedRenderer.renderer.dispose();
640
+ }
494
641
  }
495
642
  };
496
643
  }
@@ -1145,5 +1292,5 @@ function clamp01(value) {
1145
1292
  */
1146
1293
 
1147
1294
  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, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
1148
- //# sourceMappingURL=chunk-CYDGWNKQ.js.map
1149
- //# sourceMappingURL=chunk-CYDGWNKQ.js.map
1295
+ //# sourceMappingURL=chunk-FBXXXPLQ.js.map
1296
+ //# sourceMappingURL=chunk-FBXXXPLQ.js.map