mujoco-react 10.6.0 → 10.8.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/dist/onnx.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as ort from 'onnxruntime-web';
2
- import { bq as PolicyActionChunk } from './types-Dvtm4I0o.js';
2
+ import { bq as PolicyActionChunk } from './types-Bu8Aki_j.js';
3
3
  import 'react';
4
4
  import '@react-three/fiber';
5
5
  import 'three';
package/dist/spark.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as _sparkjsdev_spark from '@sparkjsdev/spark';
3
- import { o as SplatEnvironmentProps, r as PairedSplatEnvironmentConfig, S as SceneConfig, u as SplatEnvironmentReadiness, p as VisualScenarioConfig } from './types-Dvtm4I0o.js';
3
+ import { o as SplatEnvironmentProps, r as PairedSplatEnvironmentConfig, S as SceneConfig, u as SplatEnvironmentReadiness, p as VisualScenarioConfig } from './types-Bu8Aki_j.js';
4
4
  import 'react';
5
5
  import '@react-three/fiber';
6
6
  import 'three';
package/dist/spark.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-EN55TTGH.js';
1
+ import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-5WFYM73E.js';
2
2
  import { useThree } from '@react-three/fiber';
3
3
  import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
4
4
  import * as THREE from 'three';
@@ -310,6 +310,9 @@ interface MujocoModel {
310
310
  mesh_vert: Float32Array;
311
311
  mesh_face: Int32Array;
312
312
  mesh_normal: Float32Array;
313
+ mesh_texcoordadr?: Int32Array;
314
+ mesh_texcoord?: Float32Array;
315
+ mesh_facetexcoord?: Int32Array;
313
316
  site_bodyid: Int32Array;
314
317
  actuator_trnid: Int32Array;
315
318
  actuator_ctrlrange: Float64Array;
@@ -1586,6 +1589,13 @@ type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
1586
1589
  speed?: number;
1587
1590
  interpolate?: boolean;
1588
1591
  renderOptions?: MujocoRenderOptions;
1592
+ /**
1593
+ * Names of model bodies whose geometry should not be rendered. The bodies stay
1594
+ * in the compiled model and continue to simulate — only their meshes are
1595
+ * skipped at scene-build time, so body/joint/actuator indices are unchanged.
1596
+ * Applied on every (re)build, so toggling names cannot be lost to a rebuild.
1597
+ */
1598
+ hiddenBodies?: readonly string[];
1589
1599
  };
1590
1600
  interface SitePositionResult {
1591
1601
  position: React__default.RefObject<THREE.Vector3>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "10.6.0",
3
+ "version": "10.8.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -35,6 +35,7 @@ export function SceneRenderer({ renderOptions, ...props }: SceneRendererProps) {
35
35
  mujocoRef,
36
36
  onSelectionRef,
37
37
  hiddenBodiesRef,
38
+ hiddenBodiesVersion,
38
39
  interpolateRef,
39
40
  interpolationStateRef,
40
41
  status,
@@ -43,6 +44,7 @@ export function SceneRenderer({ renderOptions, ...props }: SceneRendererProps) {
43
44
  const bodyRefs = useRef<(THREE.Group | null)[]>([]);
44
45
  const prevModelRef = useRef<MujocoModel | null>(null);
45
46
  const prevRenderOptionsKeyRef = useRef<string | null>(null);
47
+ const prevHiddenVersionRef = useRef<number | null>(null);
46
48
  const renderOptionsKey = getRenderOptionsKey(renderOptions);
47
49
 
48
50
  const geomBuilder = useMemo(() => {
@@ -57,15 +59,17 @@ export function SceneRenderer({ renderOptions, ...props }: SceneRendererProps) {
57
59
  const group = groupRef.current;
58
60
  if (!model || !group) return;
59
61
 
60
- // Skip if model hasn't changed
62
+ // Skip if neither the model, render options, nor the hidden-body set changed
61
63
  if (
62
64
  prevModelRef.current === model &&
63
- prevRenderOptionsKeyRef.current === renderOptionsKey
65
+ prevRenderOptionsKeyRef.current === renderOptionsKey &&
66
+ prevHiddenVersionRef.current === hiddenBodiesVersion
64
67
  ) {
65
68
  return;
66
69
  }
67
70
  prevModelRef.current = model;
68
71
  prevRenderOptionsKeyRef.current = renderOptionsKey;
72
+ prevHiddenVersionRef.current = hiddenBodiesVersion;
69
73
 
70
74
  // Clear previous bodies
71
75
  while (group.children.length > 0) {
@@ -92,7 +96,7 @@ export function SceneRenderer({ renderOptions, ...props }: SceneRendererProps) {
92
96
  refs.push(bodyGroup);
93
97
  }
94
98
  bodyRefs.current = refs;
95
- }, [status, geomBuilder, mjModelRef, renderOptionsKey]);
99
+ }, [status, geomBuilder, mjModelRef, renderOptionsKey, hiddenBodiesRef, hiddenBodiesVersion]);
96
100
 
97
101
  const syncBodiesToData = useCallback(() => {
98
102
  const data = mjDataRef.current;
@@ -32,6 +32,7 @@ export const MujocoCanvas = forwardRef<MujocoSimAPI, MujocoCanvasProps>(
32
32
  speed,
33
33
  interpolate,
34
34
  renderOptions,
35
+ hiddenBodies,
35
36
  loadingFallback,
36
37
  children,
37
38
  ...canvasProps
@@ -75,6 +76,7 @@ export const MujocoCanvas = forwardRef<MujocoSimAPI, MujocoCanvasProps>(
75
76
  speed={speed}
76
77
  interpolate={interpolate}
77
78
  renderOptions={renderOptions}
79
+ hiddenBodies={hiddenBodies}
78
80
  >
79
81
  {children}
80
82
  </MujocoSimProvider>
@@ -441,6 +441,8 @@ export interface MujocoSimContextValue {
441
441
  errorRef: React.RefObject<string | null>;
442
442
  bodyRegistryRef: React.RefObject<Map<string, { definition: SceneObject; hasCustomChildren: boolean }>>;
443
443
  hiddenBodiesRef: React.RefObject<Set<string>>;
444
+ /** Bumped whenever the hidden-body set changes, forcing SceneRenderer to rebuild. */
445
+ hiddenBodiesVersion: number;
444
446
  requestBodyReload: () => void;
445
447
  status: 'loading' | 'ready' | 'error';
446
448
  }
@@ -547,6 +549,7 @@ interface MujocoSimProviderProps {
547
549
  speed?: number;
548
550
  interpolate?: boolean;
549
551
  renderOptions?: MujocoRenderOptions;
552
+ hiddenBodies?: readonly string[];
550
553
  children: React.ReactNode;
551
554
  }
552
555
 
@@ -565,6 +568,7 @@ export function MujocoSimProvider({
565
568
  speed,
566
569
  interpolate,
567
570
  renderOptions,
571
+ hiddenBodies,
568
572
  children,
569
573
  }: MujocoSimProviderProps) {
570
574
  const { gl, camera, scene } = useThree();
@@ -602,6 +606,8 @@ export function MujocoSimProvider({
602
606
  const errorRef = useRef<string | null>(null);
603
607
  const bodyRegistryRef = useRef(new Map<string, { definition: SceneObject; hasCustomChildren: boolean }>());
604
608
  const hiddenBodiesRef = useRef(new Set<string>());
609
+ const providedHiddenBodiesRef = useRef<Set<string>>(new Set(hiddenBodies ?? []));
610
+ const [hiddenBodiesVersion, setHiddenBodiesVersion] = useState(0);
605
611
  const bodyReloadTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
606
612
 
607
613
  configRef.current = config;
@@ -629,16 +635,43 @@ export function MujocoSimProvider({
629
635
  model.opt.timestep = timestep;
630
636
  }, [timestep]);
631
637
 
638
+ // Sync hiddenBodies prop. Rebuilds the render meshes (not the model) when the
639
+ // set changes so a toggle can never be lost to SceneRenderer's own rebuilds.
640
+ const hiddenBodiesKey = (hiddenBodies ?? []).join('');
641
+ useEffect(() => {
642
+ const next = new Set(hiddenBodies ?? []);
643
+ const prev = providedHiddenBodiesRef.current;
644
+ let unchanged = next.size === prev.size;
645
+ if (unchanged) {
646
+ for (const name of next) {
647
+ if (!prev.has(name)) { unchanged = false; break; }
648
+ }
649
+ }
650
+ if (unchanged) return;
651
+ providedHiddenBodiesRef.current = next;
652
+ recomputeHiddenBodies();
653
+ setHiddenBodiesVersion(v => v + 1);
654
+ // eslint-disable-next-line react-hooks/exhaustive-deps
655
+ }, [hiddenBodiesKey]);
656
+
657
+ // Recompute the set of bodies SceneRenderer must skip: bodies whose rendering
658
+ // a <Body> replaces with custom children, plus any named via the hiddenBodies
659
+ // prop. SceneRenderer reads this ref on every build.
660
+ function recomputeHiddenBodies() {
661
+ hiddenBodiesRef.current.clear();
662
+ for (const [name, entry] of bodyRegistryRef.current) {
663
+ if (entry.hasCustomChildren) hiddenBodiesRef.current.add(name);
664
+ }
665
+ for (const name of providedHiddenBodiesRef.current) hiddenBodiesRef.current.add(name);
666
+ }
667
+
632
668
  // --- Build merged config (base + body registry) ---
633
669
  function buildMergedConfig(baseConfig: SceneConfig): SceneConfig {
670
+ recomputeHiddenBodies();
634
671
  if (bodyRegistryRef.current.size === 0) return baseConfig;
635
672
  const registeredNames = new Set(bodyRegistryRef.current.keys());
636
673
  const baseObjects = (baseConfig.sceneObjects ?? []).filter(o => !registeredNames.has(o.name));
637
674
  const registeredBodies = Array.from(bodyRegistryRef.current.values()).map(e => e.definition);
638
- hiddenBodiesRef.current.clear();
639
- for (const [name, entry] of bodyRegistryRef.current) {
640
- if (entry.hasCustomChildren) hiddenBodiesRef.current.add(name);
641
- }
642
675
  return { ...baseConfig, sceneObjects: [...baseObjects, ...registeredBodies] };
643
676
  }
644
677
 
@@ -1967,10 +2000,11 @@ export function MujocoSimProvider({
1967
2000
  errorRef,
1968
2001
  bodyRegistryRef,
1969
2002
  hiddenBodiesRef,
2003
+ hiddenBodiesVersion,
1970
2004
  requestBodyReload,
1971
2005
  status,
1972
2006
  }),
1973
- [api, status, requestBodyReload]
2007
+ [api, status, requestBodyReload, hiddenBodiesVersion]
1974
2008
  );
1975
2009
 
1976
2010
  return (
Binary file
@@ -61,7 +61,20 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
61
61
  const ikTargetRef = useRef<THREE.Group>(new THREE.Group());
62
62
  const siteIdRef = useRef(-1);
63
63
  const controlGroupRef = useRef<ControlGroupInfo | null>(null);
64
- const genericIkRef = useRef<GenericIK>(new GenericIK(mujocoRef.current));
64
+ // The solver must always call into the module that owns the live
65
+ // model/data. Capturing the module once at mount breaks when the provider
66
+ // remounts (e.g. StrictMode double-mount): embind then rejects the model
67
+ // with "Expected null or instance of MjModel, got an instance of MjModel".
68
+ const genericIkRef = useRef<GenericIK | null>(null);
69
+ const genericIkModuleRef = useRef<unknown>(null);
70
+ const getGenericIk = useCallback(() => {
71
+ const module = mujocoRef.current;
72
+ if (!genericIkRef.current || genericIkModuleRef.current !== module) {
73
+ genericIkRef.current = new GenericIK(module);
74
+ genericIkModuleRef.current = module;
75
+ }
76
+ return genericIkRef.current;
77
+ }, [mujocoRef]);
65
78
  const firstIkEnableRef = useRef(true);
66
79
  const needsInitialSync = useRef(true);
67
80
 
@@ -119,7 +132,7 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
119
132
  const data = mjDataRef.current;
120
133
  const controlGroup = controlGroupRef.current;
121
134
  if (!model || !data || !controlGroup || siteIdRef.current === -1) return null;
122
- return genericIkRef.current.solve(
135
+ return getGenericIk().solve(
123
136
  model, data, siteIdRef.current, controlGroup.qposAdr,
124
137
  targetPosition, targetQuaternion, currentQ,
125
138
  {
@@ -135,7 +148,7 @@ export const useIkController = createControllerHook<IkConfig, IkContextValue>(
135
148
  },
136
149
  );
137
150
  },
138
- [config, mjModelRef, mjDataRef],
151
+ [config, mjModelRef, mjDataRef, getGenericIk],
139
152
  );
140
153
  const ikSolveFnRef = useRef<IKSolveFn>(ikSolveFn);
141
154
  ikSolveFnRef.current = ikSolveFn;
package/src/index.ts CHANGED
@@ -97,11 +97,13 @@ export { useBodyState } from './hooks/useBodyState';
97
97
  export { useBodyPose, useGeomPose, useSitePose } from './hooks/usePose';
98
98
  export type { PoseReadout, PoseResourceKind } from './hooks/usePose';
99
99
  export { useCtrl } from './hooks/useCtrl';
100
- export { controlGroup, useControlGroup } from './hooks/useControlGroup';
100
+ export { controlGroup, defineControls, useControlGroup, useControls } from './hooks/useControlGroup';
101
101
  export type {
102
102
  ControlGroup,
103
103
  ControlGroupHandle,
104
104
  ControlGroupSetOptions,
105
+ ControlsHandle,
106
+ DefinedControls,
105
107
  UseControlGroupOptions,
106
108
  } from './hooks/useControlGroup';
107
109
  export { useControlWriter } from './hooks/useControlWriter';
@@ -155,16 +155,40 @@ export class GeomBuilder {
155
155
  const fAdr = mjModel.mesh_faceadr[mId];
156
156
  const fNum = mjModel.mesh_facenum[mId];
157
157
 
158
- geo = new THREE.BufferGeometry();
159
- // 'position' attribute = vertices
160
- geo.setAttribute('position', new THREE.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
161
- // 'index' = faces (triangles connecting vertices)
162
- geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
163
- const smoothingTolerance = this.getMeshNormalSmoothingTolerance();
164
- if (smoothingTolerance !== null) {
165
- geo = mergeVertices(geo, smoothingTolerance);
158
+ const tAdr = mjModel.mesh_texcoordadr?.[mId] ?? -1;
159
+ if (map && tAdr >= 0 && mjModel.mesh_texcoord && mjModel.mesh_facetexcoord) {
160
+ // Textured mesh. MuJoCo indexes texcoords per face corner
161
+ // (mesh_facetexcoord), independently of the vertex indices, so
162
+ // the geometry must be de-indexed to carry a 'uv' attribute.
163
+ const corners = fNum * 3;
164
+ const positions = new Float32Array(corners * 3);
165
+ const uvs = new Float32Array(corners * 2);
166
+ for (let c = 0; c < corners; c++) {
167
+ const v = vAdr + mjModel.mesh_face[fAdr * 3 + c];
168
+ positions[c * 3] = mjModel.mesh_vert[v * 3];
169
+ positions[c * 3 + 1] = mjModel.mesh_vert[v * 3 + 1];
170
+ positions[c * 3 + 2] = mjModel.mesh_vert[v * 3 + 2];
171
+ const t = tAdr + mjModel.mesh_facetexcoord[fAdr * 3 + c];
172
+ uvs[c * 2] = mjModel.mesh_texcoord[t * 2];
173
+ // MuJoCo stores V with image-top origin; flip back to GL convention.
174
+ uvs[c * 2 + 1] = 1 - mjModel.mesh_texcoord[t * 2 + 1];
175
+ }
176
+ geo = new THREE.BufferGeometry();
177
+ geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
178
+ geo.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
179
+ geo.computeVertexNormals();
180
+ } else {
181
+ geo = new THREE.BufferGeometry();
182
+ // 'position' attribute = vertices
183
+ geo.setAttribute('position', new THREE.Float32BufferAttribute(mjModel.mesh_vert.subarray(vAdr * 3, (vAdr + vNum) * 3), 3));
184
+ // 'index' = faces (triangles connecting vertices)
185
+ geo.setIndex(Array.from(mjModel.mesh_face.subarray(fAdr * 3, (fAdr + fNum) * 3)));
186
+ const smoothingTolerance = this.getMeshNormalSmoothingTolerance();
187
+ if (smoothingTolerance !== null) {
188
+ geo = mergeVertices(geo, smoothingTolerance);
189
+ }
190
+ geo.computeVertexNormals(); // Auto-calculate smooth lighting normals
166
191
  }
167
- geo.computeVertexNormals(); // Auto-calculate smooth lighting normals
168
192
  }
169
193
 
170
194
  // 5. Construct the final Mesh
package/src/types.ts CHANGED
@@ -291,6 +291,9 @@ export interface MujocoModel {
291
291
  mesh_vert: Float32Array;
292
292
  mesh_face: Int32Array;
293
293
  mesh_normal: Float32Array;
294
+ mesh_texcoordadr?: Int32Array;
295
+ mesh_texcoord?: Float32Array;
296
+ mesh_facetexcoord?: Int32Array;
294
297
 
295
298
  // Site
296
299
  site_bodyid: Int32Array;
@@ -1843,6 +1846,13 @@ export type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
1843
1846
  speed?: number;
1844
1847
  interpolate?: boolean;
1845
1848
  renderOptions?: MujocoRenderOptions;
1849
+ /**
1850
+ * Names of model bodies whose geometry should not be rendered. The bodies stay
1851
+ * in the compiled model and continue to simulate — only their meshes are
1852
+ * skipped at scene-build time, so body/joint/actuator indices are unchanged.
1853
+ * Applied on every (re)build, so toggling names cannot be lost to a rebuild.
1854
+ */
1855
+ hiddenBodies?: readonly string[];
1846
1856
  };
1847
1857
 
1848
1858
  // ---- Hook Return Types ----