mujoco-react 4.0.0 → 6.0.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
@@ -27,7 +27,7 @@ import type { SceneConfig } from 'mujoco-react';
27
27
  import { OrbitControls } from '@react-three/drei';
28
28
 
29
29
  const config: SceneConfig = {
30
- modelId: 'franka_emika_panda',
30
+ src: 'https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/',
31
31
  sceneFile: 'scene.xml',
32
32
  homeJoints: [1.707, -1.754, 0.003, -2.702, 0.003, 0.951, 2.490],
33
33
  };
@@ -178,39 +178,28 @@ const ikCtx = useIk({ optional: true });
178
178
 
179
179
  ## Loading Models
180
180
 
181
- Models are loaded from any HTTP source via `SceneConfig.baseUrl`. Defaults to [MuJoCo Menagerie](https://github.com/google-deepmind/mujoco_menagerie) on GitHub.
181
+ The loader fetches `src + sceneFile`, parses the XML for dependencies (meshes, textures, includes), recursively fetches those too, and writes everything to MuJoCo's in-memory WASM filesystem.
182
182
 
183
183
  ```tsx
184
- // Menagerie models: just set modelId
184
+ // MuJoCo Menagerie
185
185
  const franka: SceneConfig = {
186
- modelId: 'franka_emika_panda',
186
+ src: 'https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/',
187
187
  sceneFile: 'scene.xml',
188
188
  };
189
189
 
190
- // Any GitHub repo
191
- const so101: SceneConfig = {
192
- modelId: 'so101',
193
- sceneFile: 'SO101.xml',
194
- baseUrl: 'https://raw.githubusercontent.com/your-org/your-repo/main/models/',
195
- };
196
-
197
- // Self-hosted
190
+ // Any URL
198
191
  const custom: SceneConfig = {
199
- modelId: 'my_robot',
200
- sceneFile: 'robot.xml',
201
- baseUrl: 'http://localhost:3000/models/my_robot/',
192
+ src: 'http://localhost:3000/models/my_model/',
193
+ sceneFile: 'model.xml',
202
194
  };
203
195
  ```
204
196
 
205
- The loader fetches the scene XML, parses it for dependencies (meshes, textures, includes), recursively fetches those too, applies any XML patches, and writes everything to MuJoCo's in-memory WASM filesystem.
206
-
207
197
  ## SceneConfig
208
198
 
209
199
  ```ts
210
200
  interface SceneConfig {
211
- modelId: string; // e.g. 'franka_emika_panda'
201
+ src: string; // Base URL for model files
212
202
  sceneFile: string; // Entry XML file, e.g. 'scene.xml'
213
- baseUrl?: string; // Base URL for fetching model files
214
203
  sceneObjects?: SceneObject[]; // Objects injected into scene XML at load time
215
204
  homeJoints?: number[]; // Initial joint positions
216
205
  xmlPatches?: XmlPatch[]; // Patches applied to XML files during loading
@@ -222,7 +211,7 @@ interface SceneConfig {
222
211
 
223
212
  ```tsx
224
213
  const config: SceneConfig = {
225
- modelId: 'franka_emika_panda',
214
+ src: 'https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/',
226
215
  sceneFile: 'scene.xml',
227
216
  sceneObjects: [
228
217
  { name: 'ball', type: 'sphere', size: [0.03, 0.03, 0.03],
@@ -376,10 +365,6 @@ Component wrapper for contact events:
376
365
  />
377
366
  ```
378
367
 
379
- ### `<SelectionHighlight />`
380
-
381
- Emissive highlight on selected body meshes. Also available as `useSelectionHighlight(bodyId, options?)` hook.
382
-
383
368
  ### `<TrajectoryPlayer />`
384
369
 
385
370
  Plays back recorded qpos trajectories with scrubbing.
@@ -566,9 +551,19 @@ Returns actuator metadata for building control UIs.
566
551
 
567
552
  Ref-based site position/quaternion tracking.
568
553
 
554
+ ### `useBodyMeshes(bodyId)`
555
+
556
+ Returns the Three.js meshes belonging to a MuJoCo body. Use for custom selection visuals, outlines, postprocessing, or any per-body mesh manipulation:
557
+
558
+ ```tsx
559
+ const meshes = useBodyMeshes(selectedBodyId);
560
+
561
+ // Use with drei Outline, or manipulate materials directly
562
+ ```
563
+
569
564
  ### `useSelectionHighlight(bodyId, options?)`
570
565
 
571
- Hook form of `<SelectionHighlight>`. Apply emissive highlights imperatively:
566
+ Convenience wrapper around `useBodyMeshes` that applies an emissive highlight:
572
567
 
573
568
  ```tsx
574
569
  useSelectionHighlight(selectedBodyId, { color: '#00ff00', emissiveIntensity: 0.5 });
@@ -670,12 +665,13 @@ Objects that need stable contact (grasping, stacking, etc.) require tuned MuJoCo
670
665
 
671
666
  ### Click-to-Select
672
667
 
673
- Combine R3F raycasting with `<SelectionHighlight />` for body selection:
668
+ Combine R3F raycasting with `useSelectionHighlight` for body selection:
674
669
 
675
670
  ```tsx
676
671
  function ClickSelectOverlay() {
677
672
  const selectedBodyId = useClickSelect(); // your raycasting hook
678
- return <SelectionHighlight bodyId={selectedBodyId} />;
673
+ useSelectionHighlight(selectedBodyId);
674
+ return null;
679
675
  }
680
676
  ```
681
677
 
package/dist/index.d.ts CHANGED
@@ -233,9 +233,10 @@ interface XmlPatch {
233
233
  replace?: [string, string];
234
234
  }
235
235
  interface SceneConfig {
236
- modelId: string;
236
+ /** Base URL for fetching model files. The loader fetches `src + sceneFile` and follows dependencies. */
237
+ src: string;
238
+ /** Entry MJCF XML file name, e.g. 'scene.xml'. */
237
239
  sceneFile: string;
238
- baseUrl?: string;
239
240
  sceneObjects?: SceneObject[];
240
241
  homeJoints?: number[];
241
242
  xmlPatches?: XmlPatch[];
@@ -392,11 +393,6 @@ interface TrajectoryPlayerProps {
392
393
  playing?: boolean;
393
394
  onFrame?: (frameIdx: number) => void;
394
395
  }
395
- interface SelectionHighlightProps {
396
- bodyId: number | null;
397
- color?: string;
398
- emissiveIntensity?: number;
399
- }
400
396
  interface ContactListenerProps {
401
397
  body: string;
402
398
  onContactEnter?: (info: ContactInfo) => void;
@@ -815,19 +811,6 @@ declare function ContactListener({ body, onContactEnter, onContactExit, }: Conta
815
811
  */
816
812
  declare function TrajectoryPlayer({ trajectory, fps, loop, playing, onFrame, }: TrajectoryPlayerProps): null;
817
813
 
818
- /**
819
- * @license
820
- * SPDX-License-Identifier: Apache-2.0
821
- *
822
- * SelectionHighlight — highlight a selected body with emissive color (spec 6.5)
823
- */
824
-
825
- /**
826
- * Applies emissive highlight to all meshes belonging to a body.
827
- * Restores original emissive when bodyId changes or component unmounts.
828
- */
829
- declare function SelectionHighlight({ bodyId, color, emissiveIntensity, }: SelectionHighlightProps): null;
830
-
831
814
  /**
832
815
  * @license
833
816
  * SPDX-License-Identifier: Apache-2.0
@@ -1113,10 +1096,37 @@ declare function useCtrlNoise(config?: CtrlNoiseConfig): void;
1113
1096
  * @license
1114
1097
  * SPDX-License-Identifier: Apache-2.0
1115
1098
  *
1116
- * useSelectionHighlighthook form of SelectionHighlight (spec 6.5)
1099
+ * useBodyMeshesreturns Three.js meshes belonging to a MuJoCo body.
1100
+ *
1101
+ * Low-level primitive for custom selection visuals, outlines,
1102
+ * postprocessing effects, or any per-body mesh manipulation.
1103
+ */
1104
+
1105
+ /**
1106
+ * Returns all Three.js meshes belonging to the given MuJoCo body ID.
1107
+ *
1108
+ * @example
1109
+ * ```tsx
1110
+ * const meshes = useBodyMeshes(selectedBodyId);
1111
+ *
1112
+ * // Use with drei Outline
1113
+ * <Outline selection={meshes} />
1114
+ *
1115
+ * // Or manipulate directly
1116
+ * useFrame(() => {
1117
+ * meshes.forEach(m => { m.scale.setScalar(1.05); });
1118
+ * });
1119
+ * ```
1120
+ */
1121
+ declare function useBodyMeshes(bodyId: number | null): THREE.Mesh[];
1122
+
1123
+ /**
1124
+ * @license
1125
+ * SPDX-License-Identifier: Apache-2.0
1126
+ *
1127
+ * useSelectionHighlight — convenience hook for emissive body highlights.
1117
1128
  *
1118
- * Applies emissive highlight to all meshes belonging to a body.
1119
- * Restores original emissive when bodyId changes or hook unmounts.
1129
+ * Built on useBodyMeshes. For custom visuals, use useBodyMeshes directly.
1120
1130
  */
1121
1131
  declare function useSelectionHighlight(bodyId: number | null, options?: {
1122
1132
  color?: string;
@@ -1154,4 +1164,4 @@ interface CameraAnimationAPI {
1154
1164
  */
1155
1165
  declare function useCameraAnimation(): CameraAnimationAPI;
1156
1166
 
1157
- export { type ActuatorInfo, type BodyInfo, type BodyStateResult, type CameraAnimationAPI, type ContactInfo, ContactListener, type ContactListenerProps, ContactMarkers, type ControllerComponent, type ControllerOptions, Debug, type DebugProps, DragInteraction, type DragInteractionProps, FlexRenderer, type GeomInfo, type IKSolveFn, type IkConfig, type IkContextValue, IkController, IkGizmo, type IkGizmoProps, type JointInfo, type JointStateResult, type KeyBinding, type KeyboardTeleopConfig, type ModelOptions, MujocoCanvas, type MujocoCanvasProps, type MujocoContact, type MujocoContactArray, type MujocoContextValue, type MujocoData, type MujocoModel, type MujocoModule, MujocoPhysics, type MujocoPhysicsProps, MujocoProvider, type MujocoSimAPI, MujocoSimProvider, type PhysicsConfig, type PhysicsStepCallback, type PolicyConfig, type RayHit, type SceneConfig, SceneLights, type SceneLightsProps, type SceneMarker, type SceneObject, SelectionHighlight, type SelectionHighlightProps, type SensorInfo, type SensorResult, type SiteInfo, type SitePositionResult, type StateSnapshot, TendonRenderer, type TrajectoryData, type TrajectoryFrame, TrajectoryPlayer, type TrajectoryPlayerProps, type XmlPatch, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIk, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
1167
+ export { type ActuatorInfo, type BodyInfo, type BodyStateResult, type CameraAnimationAPI, type ContactInfo, ContactListener, type ContactListenerProps, ContactMarkers, type ControllerComponent, type ControllerOptions, Debug, type DebugProps, DragInteraction, type DragInteractionProps, FlexRenderer, type GeomInfo, type IKSolveFn, type IkConfig, type IkContextValue, IkController, IkGizmo, type IkGizmoProps, type JointInfo, type JointStateResult, type KeyBinding, type KeyboardTeleopConfig, type ModelOptions, MujocoCanvas, type MujocoCanvasProps, type MujocoContact, type MujocoContactArray, type MujocoContextValue, type MujocoData, type MujocoModel, type MujocoModule, MujocoPhysics, type MujocoPhysicsProps, MujocoProvider, type MujocoSimAPI, MujocoSimProvider, type PhysicsConfig, type PhysicsStepCallback, type PolicyConfig, type RayHit, type SceneConfig, SceneLights, type SceneLightsProps, type SceneMarker, type SceneObject, type SensorInfo, type SensorResult, type SiteInfo, type SitePositionResult, type StateSnapshot, TendonRenderer, type TrajectoryData, type TrajectoryFrame, TrajectoryPlayer, type TrajectoryPlayerProps, type XmlPatch, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIk, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
package/dist/index.js CHANGED
@@ -400,7 +400,7 @@ async function loadScene(mujoco, config, onProgress) {
400
400
  mujoco.FS.mkdir("/working");
401
401
  } catch {
402
402
  }
403
- const baseUrl = config.baseUrl || `https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/${config.modelId}/`;
403
+ const baseUrl = config.src.endsWith("/") ? config.src : config.src + "/";
404
404
  const downloaded = /* @__PURE__ */ new Set();
405
405
  const queue = [config.sceneFile];
406
406
  const parser = new DOMParser();
@@ -3053,58 +3053,6 @@ function TrajectoryPlayer({
3053
3053
  });
3054
3054
  return null;
3055
3055
  }
3056
- function useSelectionHighlight(bodyId, options = {}) {
3057
- const { color = "#ff4444", emissiveIntensity = 0.3 } = options;
3058
- const { scene } = useThree();
3059
- const prevMeshesRef = useRef([]);
3060
- useEffect(() => {
3061
- for (const entry of prevMeshesRef.current) {
3062
- const mat = entry.mesh.material;
3063
- if (mat.emissive) {
3064
- mat.emissive.copy(entry.originalEmissive);
3065
- mat.emissiveIntensity = entry.originalIntensity;
3066
- }
3067
- }
3068
- prevMeshesRef.current = [];
3069
- if (bodyId === null || bodyId < 0) return;
3070
- const highlightColor = new THREE11.Color(color);
3071
- scene.traverse((obj) => {
3072
- if (obj.userData.bodyID === bodyId && obj.isMesh) {
3073
- const mesh = obj;
3074
- const mat = mesh.material;
3075
- if (mat.emissive) {
3076
- prevMeshesRef.current.push({
3077
- mesh,
3078
- originalEmissive: mat.emissive.clone(),
3079
- originalIntensity: mat.emissiveIntensity ?? 0
3080
- });
3081
- mat.emissive.copy(highlightColor);
3082
- mat.emissiveIntensity = emissiveIntensity;
3083
- }
3084
- }
3085
- });
3086
- return () => {
3087
- for (const entry of prevMeshesRef.current) {
3088
- const mat = entry.mesh.material;
3089
- if (mat.emissive) {
3090
- mat.emissive.copy(entry.originalEmissive);
3091
- mat.emissiveIntensity = entry.originalIntensity;
3092
- }
3093
- }
3094
- prevMeshesRef.current = [];
3095
- };
3096
- }, [bodyId, color, emissiveIntensity, scene]);
3097
- }
3098
-
3099
- // src/components/SelectionHighlight.tsx
3100
- function SelectionHighlight({
3101
- bodyId,
3102
- color = "#ff4444",
3103
- emissiveIntensity = 0.3
3104
- }) {
3105
- useSelectionHighlight(bodyId, { color, emissiveIntensity });
3106
- return null;
3107
- }
3108
3056
  function useActuators() {
3109
3057
  const { mjModelRef, status } = useMujoco();
3110
3058
  return useMemo(() => {
@@ -3651,6 +3599,57 @@ function useCtrlNoise(config = {}) {
3651
3599
  }
3652
3600
  });
3653
3601
  }
3602
+ function useBodyMeshes(bodyId) {
3603
+ const { scene } = useThree();
3604
+ return useMemo(() => {
3605
+ if (bodyId === null || bodyId < 0) return [];
3606
+ const meshes = [];
3607
+ scene.traverse((obj) => {
3608
+ if (obj.userData.bodyID === bodyId && obj.isMesh) {
3609
+ meshes.push(obj);
3610
+ }
3611
+ });
3612
+ return meshes;
3613
+ }, [bodyId, scene]);
3614
+ }
3615
+ function useSelectionHighlight(bodyId, options = {}) {
3616
+ const { color = "#ff4444", emissiveIntensity = 0.3 } = options;
3617
+ const meshes = useBodyMeshes(bodyId);
3618
+ const prevRef = useRef([]);
3619
+ useEffect(() => {
3620
+ for (const entry of prevRef.current) {
3621
+ const mat = entry.mesh.material;
3622
+ if (mat.emissive) {
3623
+ mat.emissive.copy(entry.originalEmissive);
3624
+ mat.emissiveIntensity = entry.originalIntensity;
3625
+ }
3626
+ }
3627
+ prevRef.current = [];
3628
+ const highlightColor = new THREE11.Color(color);
3629
+ for (const mesh of meshes) {
3630
+ const mat = mesh.material;
3631
+ if (mat.emissive) {
3632
+ prevRef.current.push({
3633
+ mesh,
3634
+ originalEmissive: mat.emissive.clone(),
3635
+ originalIntensity: mat.emissiveIntensity ?? 0
3636
+ });
3637
+ mat.emissive.copy(highlightColor);
3638
+ mat.emissiveIntensity = emissiveIntensity;
3639
+ }
3640
+ }
3641
+ return () => {
3642
+ for (const entry of prevRef.current) {
3643
+ const mat = entry.mesh.material;
3644
+ if (mat.emissive) {
3645
+ mat.emissive.copy(entry.originalEmissive);
3646
+ mat.emissiveIntensity = entry.originalIntensity;
3647
+ }
3648
+ }
3649
+ prevRef.current = [];
3650
+ };
3651
+ }, [meshes, color, emissiveIntensity]);
3652
+ }
3654
3653
  function useCameraAnimation() {
3655
3654
  const { camera } = useThree();
3656
3655
  const orbitTargetRef = useRef(new THREE11.Vector3(0, 0, 0));
@@ -3825,21 +3824,6 @@ function useCameraAnimation() {
3825
3824
  *
3826
3825
  * TrajectoryPlayer — component form of trajectory playback (spec 13.2)
3827
3826
  */
3828
- /**
3829
- * @license
3830
- * SPDX-License-Identifier: Apache-2.0
3831
- *
3832
- * useSelectionHighlight — hook form of SelectionHighlight (spec 6.5)
3833
- *
3834
- * Applies emissive highlight to all meshes belonging to a body.
3835
- * Restores original emissive when bodyId changes or hook unmounts.
3836
- */
3837
- /**
3838
- * @license
3839
- * SPDX-License-Identifier: Apache-2.0
3840
- *
3841
- * SelectionHighlight — highlight a selected body with emissive color (spec 6.5)
3842
- */
3843
3827
  /**
3844
3828
  * @license
3845
3829
  * SPDX-License-Identifier: Apache-2.0
@@ -3900,6 +3884,23 @@ function useCameraAnimation() {
3900
3884
  *
3901
3885
  * useCtrlNoise — control noise / perturbation hook (spec 3.2)
3902
3886
  */
3887
+ /**
3888
+ * @license
3889
+ * SPDX-License-Identifier: Apache-2.0
3890
+ *
3891
+ * useBodyMeshes — returns Three.js meshes belonging to a MuJoCo body.
3892
+ *
3893
+ * Low-level primitive for custom selection visuals, outlines,
3894
+ * postprocessing effects, or any per-body mesh manipulation.
3895
+ */
3896
+ /**
3897
+ * @license
3898
+ * SPDX-License-Identifier: Apache-2.0
3899
+ *
3900
+ * useSelectionHighlight — convenience hook for emissive body highlights.
3901
+ *
3902
+ * Built on useBodyMeshes. For custom visuals, use useBodyMeshes directly.
3903
+ */
3903
3904
  /**
3904
3905
  * @license
3905
3906
  * SPDX-License-Identifier: Apache-2.0
@@ -3907,6 +3908,6 @@ function useCameraAnimation() {
3907
3908
  * useCameraAnimation — composable camera animation hook.
3908
3909
  */
3909
3910
 
3910
- export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkController, IkGizmo, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, SelectionHighlight, TendonRenderer, TrajectoryPlayer, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIk, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
3911
+ export { ContactListener, ContactMarkers, Debug, DragInteraction, FlexRenderer, IkController, IkGizmo, MujocoCanvas, MujocoPhysics, MujocoProvider, MujocoSimProvider, SceneLights, TendonRenderer, TrajectoryPlayer, createController, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getContact, getName, loadScene, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useGamepad, useGravityCompensation, useIk, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
3911
3912
  //# sourceMappingURL=index.js.map
3912
3913
  //# sourceMappingURL=index.js.map