mujoco-react 1.0.0 → 3.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
@@ -1,7 +1,12 @@
1
+ <p align="center">
2
+ <img src="docs/images/mj2.gif" alt="mujoco-react demo" width="100%" />
3
+ </p>
4
+
1
5
  # mujoco-react
2
6
 
3
- A thin, unopinionated wrapper around [mujoco-js](https://github.com/nicepkg/mujoco-js) composable and extensible via React. Built on [React Three Fiber](https://docs.pmnd.rs/react-three-fiber). Works with **any robot, any scene**.
7
+ Composable [React Three Fiber](https://docs.pmnd.rs/react-three-fiber) wrapper around [mujoco-js](https://www.npmjs.com/package/mujoco-js). Load any MuJoCo model, step physics, render bodies, and write controllers as React components.
4
8
 
9
+ **[Live Demo](https://mujoco-react-example.pages.dev)** | **[Docs](https://dadd.mintlify.app)** | **[Example Source](https://github.com/noah-wardlow/mujoco-react-example)**
5
10
 
6
11
  ## Install
7
12
 
@@ -15,11 +20,10 @@ npm install mujoco-react three @react-three/fiber @react-three/drei
15
20
  import {
16
21
  MujocoProvider,
17
22
  MujocoCanvas,
18
- SceneRenderer,
19
23
  IkController,
20
24
  IkGizmo,
21
25
  } from 'mujoco-react';
22
- import type { SceneConfig, MujocoSimAPI } from 'mujoco-react';
26
+ import type { SceneConfig } from 'mujoco-react';
23
27
  import { OrbitControls } from '@react-three/drei';
24
28
 
25
29
  const config: SceneConfig = {
@@ -29,19 +33,15 @@ const config: SceneConfig = {
29
33
  };
30
34
 
31
35
  function App() {
32
- const apiRef = useRef<MujocoSimAPI>(null);
33
-
34
36
  return (
35
37
  <MujocoProvider>
36
38
  <MujocoCanvas
37
- ref={apiRef}
38
39
  config={config}
39
40
  camera={{ position: [2, -1.5, 2.5], up: [0, 0, 1], fov: 45 }}
40
41
  shadows
41
42
  style={{ width: '100%', height: '100vh' }}
42
43
  >
43
44
  <OrbitControls enableDamping makeDefault />
44
- <SceneRenderer />
45
45
  <IkController config={{ siteName: 'tcp', numJoints: 7 }}>
46
46
  <IkGizmo />
47
47
  </IkController>
@@ -53,49 +53,29 @@ function App() {
53
53
  }
54
54
  ```
55
55
 
56
- ## Architecture
57
-
58
- Two ways to set up your scene:
59
-
60
- ### `<MujocoCanvas>` — Quick Start
56
+ ## `useMujoco()`
61
57
 
62
- Wraps R3F `<Canvas>` for you. Fastest path to a working scene:
58
+ Inside `<MujocoCanvas>` or `<MujocoPhysics>`, `useMujoco()` gives you the simulation API, refs to the live model/data, and status:
63
59
 
64
- ```
65
- <MujocoProvider> <- WASM module lifecycle
66
- <MujocoCanvas config={...}> <- R3F Canvas + physics context
67
- <SceneRenderer /> <- Syncs MuJoCo bodies to Three.js meshes
68
- <IkController config={..}> <- Opt-in controller plugin
69
- <IkGizmo />
70
- </IkController>
71
- <YourController /> <- Bring your own controller
72
- <YourLights /> <- You compose your own scene
73
- </MujocoCanvas>
74
- </MujocoProvider>
75
- ```
60
+ ```tsx
61
+ import { useMujoco } from 'mujoco-react';
76
62
 
77
- ### `<MujocoPhysics>` — Bring Your Own Canvas
63
+ function StatusOverlay() {
64
+ const { status, api, mjModelRef, mjDataRef } = useMujoco();
78
65
 
79
- Use inside your own `<Canvas>` for full control over gl settings, post-processing, and R3F context composition:
66
+ if (status !== 'ready') return <span>Loading...</span>;
80
67
 
68
+ return (
69
+ <button onClick={() => api?.reset()}>
70
+ Reset ({mjModelRef.current?.nq} joints)
71
+ </button>
72
+ );
73
+ }
81
74
  ```
82
- <MujocoProvider>
83
- <Canvas shadows camera={...} gl={...}> <- Your Canvas, your settings
84
- <MujocoPhysics config={config}> <- Physics context only
85
- <SceneRenderer />
86
- <YourController />
87
- </MujocoPhysics>
88
- <OrbitControls />
89
- <EffectComposer>...</EffectComposer> <- Post-processing, etc.
90
- </Canvas>
91
- </MujocoProvider>
92
- ```
93
-
94
- The library provides **only MuJoCo engine concerns**: WASM lifecycle, physics stepping, and body rendering. Controllers (IK, teleoperation, RL policies) are composable plugins you opt into — or bring your own.
95
75
 
96
- ## Bring Your Own Controller
76
+ ## Writing a Controller
97
77
 
98
- **Controllers are just React components.** Write a function that calls `useBeforePhysicsStep` to drive `data.ctrl` each frame, return `null`, and drop it into your scene tree. No base class, no registration — just hooks.
78
+ A controller is a React component that calls `useBeforePhysicsStep` to write `data.ctrl` each frame:
99
79
 
100
80
  ```tsx
101
81
  import { useBeforePhysicsStep } from 'mujoco-react';
@@ -107,76 +87,68 @@ function MyController() {
107
87
  });
108
88
  return null;
109
89
  }
90
+ ```
91
+
92
+ Drop it into the tree:
110
93
 
111
- // Drop it in:
94
+ ```tsx
112
95
  <MujocoCanvas config={config}>
113
- <SceneRenderer />
114
96
  <MyController />
115
97
  </MujocoCanvas>
116
98
  ```
117
99
 
118
- This is the primary way to use the library. IK, teleoperation, RL policies, state machines — they're all just components that read input and write to `data.ctrl`.
119
-
120
- ### Bring Your Own IK
121
-
122
- The built-in `<IkController>` uses a generic Damped Least-Squares solver, but you can plug in **any** IK solver — analytical, learned, or from another library:
100
+ The `createController<TConfig>()` factory adds typed config and default merging for reusable plugins:
123
101
 
124
102
  ```tsx
125
- import type { IKSolveFn } from 'mujoco-react';
103
+ import { createController, useBeforePhysicsStep } from 'mujoco-react';
126
104
 
127
- const myIK: IKSolveFn = (pos, quat, currentQ) => {
128
- return myAnalyticalSolver(pos, currentQ); // return joint angles or null
129
- };
105
+ export const MyController = createController<{ gain: number }>(
106
+ { name: 'MyController', defaultConfig: { gain: 1.0 } },
107
+ ({ config }) => {
108
+ useBeforePhysicsStep((_model, data) => {
109
+ data.ctrl[0] = config.gain * Math.sin(data.time);
110
+ });
111
+ return null;
112
+ },
113
+ );
130
114
 
131
- <IkController config={{ siteName: 'tcp', numJoints: 7, ikSolveFn: myIK }}>
132
- <IkGizmo />
133
- </IkController>
115
+ // <MyController config={{ gain: 2.0 }} />
134
116
  ```
135
117
 
136
- Or skip `<IkController>` entirely and solve IK yourself inside `useBeforePhysicsStep`:
118
+ ## Architecture
137
119
 
138
- ```tsx
139
- function MyIKController() {
140
- useBeforePhysicsStep((model, data) => {
141
- const joints = myCustomIKSolve(model, data);
142
- if (joints) {
143
- for (let i = 0; i < joints.length; i++) data.ctrl[i] = joints[i];
144
- }
145
- });
146
- return null;
147
- }
120
+ `<MujocoCanvas>` wraps R3F `<Canvas>` and forwards all Canvas props (`camera`, `shadows`, `gl`, etc.). For full control over the Canvas, use `<MujocoPhysics>` inside your own:
121
+
122
+ ```
123
+ <MujocoProvider> <MujocoProvider>
124
+ <MujocoCanvas config={...}> <Canvas shadows gl={...}>
125
+ <IkController config={..}> <MujocoPhysics config={...}>
126
+ <IkGizmo /> <MyController />
127
+ </IkController> </MujocoPhysics>
128
+ <MyController /> <EffectComposer>...</EffectComposer>
129
+ </MujocoCanvas> </Canvas>
130
+ </MujocoProvider> </MujocoProvider>
148
131
  ```
149
132
 
150
- ### `createController<TConfig>()` Factory
133
+ ### Custom IK Solvers
151
134
 
152
- For reusable controller plugins with typed config and default merging:
135
+ The built-in `<IkController>` uses Damped Least-Squares. Pass `ikSolveFn` to swap in your own solver (analytical, learned, etc.):
153
136
 
154
137
  ```tsx
155
- import { createController, useBeforePhysicsStep } from 'mujoco-react';
156
-
157
- interface MyConfig {
158
- gain: number;
159
- targetJoint: string;
160
- }
161
-
162
- function MyControllerImpl({ config }: { config: MyConfig; children?: React.ReactNode }) {
163
- useBeforePhysicsStep((_model, data) => {
164
- data.ctrl[0] = config.gain * Math.sin(data.time);
165
- });
166
- return null;
167
- }
138
+ import type { IKSolveFn } from 'mujoco-react';
168
139
 
169
- export const MyController = createController<MyConfig>(
170
- { name: 'MyController', defaultConfig: { gain: 1.0 } },
171
- MyControllerImpl,
172
- );
140
+ const myIK: IKSolveFn = (pos, quat, currentQ) => {
141
+ return myAnalyticalSolver(pos, currentQ); // return joint angles or null
142
+ };
173
143
 
174
- // Usage: <MyController config={{ gain: 2.0, targetJoint: 'shoulder' }} />
144
+ <IkController config={{ siteName: 'tcp', numJoints: 7, ikSolveFn: myIK }}>
145
+ <IkGizmo />
146
+ </IkController>
175
147
  ```
176
148
 
177
- ### Built-in `<IkController>`
149
+ ### `<IkController>`
178
150
 
179
- The library ships one controller out of the box — an IK gizmo you can drop in for interactive end-effector control:
151
+ The library includes one controller for interactive end-effector control:
180
152
 
181
153
  ```tsx
182
154
  <IkController config={{ siteName: 'tcp', numJoints: 7 }}>
@@ -209,7 +181,7 @@ const ikCtx = useIk({ optional: true });
209
181
  Models are loaded from any HTTP source via `SceneConfig.baseUrl`. Defaults to [MuJoCo Menagerie](https://github.com/google-deepmind/mujoco_menagerie) on GitHub.
210
182
 
211
183
  ```tsx
212
- // Menagerie robots just set robotId
184
+ // Menagerie robots: just set robotId
213
185
  const franka: SceneConfig = {
214
186
  robotId: 'franka_emika_panda',
215
187
  sceneFile: 'scene.xml',
@@ -308,7 +280,6 @@ Physics provider for use inside your own R3F `<Canvas>`. Same physics props as `
308
280
  <MujocoProvider>
309
281
  <Canvas shadows camera={{ position: [2, 2, 2] }}>
310
282
  <MujocoPhysics ref={apiRef} config={config} paused={paused}>
311
- <SceneRenderer />
312
283
  <MyController />
313
284
  </MujocoPhysics>
314
285
  <OrbitControls />
@@ -329,10 +300,6 @@ Physics provider for use inside your own R3F `<Canvas>`. Same physics props as `
329
300
  | `paused` | `boolean` | Declarative pause |
330
301
  | `speed` | `number` | Simulation speed multiplier |
331
302
 
332
- ### `<SceneRenderer />`
333
-
334
- Syncs MuJoCo bodies to Three.js meshes every frame. Must be inside `<MujocoCanvas>` or `<MujocoPhysics>`.
335
-
336
303
  ### `<IkGizmo />`
337
304
 
338
305
  drei PivotControls gizmo that tracks a MuJoCo site and drives IK on drag. Must be inside `<IkController>`.
@@ -349,10 +316,9 @@ Click-drag to apply spring forces to bodies. Raycasts to find bodies, applies `F
349
316
 
350
317
  ### R3F Group Props
351
318
 
352
- All visual components (`SceneRenderer`, `DragInteraction`, `ContactMarkers`, `Debug`, `TendonRenderer`, `FlexRenderer`) accept standard R3F group props `position`, `rotation`, `scale`, `visible`, etc.
319
+ All visual components (`DragInteraction`, `ContactMarkers`, `Debug`, `TendonRenderer`, `FlexRenderer`) accept standard R3F group props like `position`, `rotation`, `scale`, `visible`.
353
320
 
354
321
  ```tsx
355
- <SceneRenderer position={[0, 0, 1]} />
356
322
  <ContactMarkers visible={showContacts} />
357
323
  <Debug showJoints scale={0.5} />
358
324
  ```
@@ -420,12 +386,29 @@ Plays back recorded qpos trajectories with scrubbing.
420
386
 
421
387
  ## Hooks
422
388
 
423
- ### `useMujocoSim()`
389
+ ### `useMujoco()`
390
+
391
+ Access the simulation API and internal refs (must be inside `<MujocoCanvas>` or `<MujocoPhysics>`):
392
+
393
+ ```tsx
394
+ const { api, status, mjModelRef, mjDataRef } = useMujoco();
395
+ ```
396
+
397
+ ### `useMujocoWasm()`
424
398
 
425
- Access the simulation API and internal refs:
399
+ Access the raw WASM module lifecycle from any child of `<MujocoProvider>`. Most users won't need this — `useMujoco()` and hooks like `useBeforePhysicsStep` handle the model/data lifecycle for you.
426
400
 
427
401
  ```tsx
428
- const { api, mjModelRef, mjDataRef } = useMujocoSim();
402
+ import { useMujocoWasm } from 'mujoco-react';
403
+
404
+ const { mujoco, status } = useMujocoWasm();
405
+
406
+ if (mujoco) {
407
+ const model = mujoco.MjModel.loadFromXML('/path/to/scene.xml');
408
+ const data = new mujoco.MjData(model);
409
+ mujoco.mj_step(model, data);
410
+ console.log(data.qpos); // joint positions after one step
411
+ }
429
412
  ```
430
413
 
431
414
  ### `useBeforePhysicsStep(callback)`
@@ -601,7 +584,7 @@ useSceneLights(1.5);
601
584
 
602
585
  ## MujocoSimAPI
603
586
 
604
- The full API object available via `ref` or `useMujocoSim().api`:
587
+ The full API object available via `ref` or `useMujoco().api`:
605
588
 
606
589
  ### Simulation Control
607
590
 
@@ -679,29 +662,11 @@ The full API object available via `ref` or `useMujocoSim().api`:
679
662
 
680
663
  ### Building Controllers
681
664
 
682
- See [Building Controllers](https://mujoco-react.mintlify.app/guides/building-controllers) for full patterns including config-driven controllers, IK gizmo coexistence, multi-arm support, and the `createController` factory.
683
-
684
- ### Graspable Objects
665
+ See [Building Controllers](https://dadd.mintlify.app/guides/building-controllers) for full patterns including config-driven controllers, IK gizmo coexistence, multi-arm support, and the `createController` factory.
685
666
 
686
- Objects need specific MuJoCo contact parameters to be picked up by grippers:
687
-
688
- ```tsx
689
- sceneObjects: [{
690
- name: 'cube',
691
- type: 'box',
692
- size: [0.025, 0.025, 0.025],
693
- position: [0.4, 0, 0.025],
694
- rgba: [0.9, 0.2, 0.15, 1],
695
- mass: 0.05,
696
- freejoint: true,
697
- friction: '1.5 0.3 0.1', // high sliding friction
698
- solref: '0.01 1', // stiff contact solver
699
- solimp: '0.95 0.99 0.001 0.5 2', // tight impedance
700
- condim: 4, // elliptic friction cone
701
- }]
702
- ```
667
+ ### Contact Parameters
703
668
 
704
- Without `condim: 4` and high friction, objects slide out of the gripper when lifted. See [Graspable Objects](https://mujoco-react.mintlify.app/guides/graspable-objects) for details.
669
+ Objects that need stable contact (grasping, stacking, etc.) require tuned MuJoCo solver parameters `friction`, `solref`, `solimp`, and `condim`. See [Contact Parameters](https://dadd.mintlify.app/guides/graspable-objects) for details.
705
670
 
706
671
  ### Click-to-Select
707
672
 
@@ -714,14 +679,14 @@ function ClickSelectOverlay() {
714
679
  }
715
680
  ```
716
681
 
717
- See [Click-to-Select](https://mujoco-react.mintlify.app/guides/click-to-select) for the full implementation.
682
+ See [Click-to-Select](https://dadd.mintlify.app/guides/click-to-select) for the full implementation.
718
683
 
719
684
  ## useFrame Priority
720
685
 
721
686
  | Priority | Owner | Purpose |
722
687
  |----------|-------|---------|
723
688
  | -1 | MujocoSimProvider | beforeStep, mj_step, afterStep |
724
- | 0 (default) | SceneRenderer, IkController, your code | Body mesh sync, IK, rendering |
689
+ | 0 (default) | SceneRenderer (internal), IkController, your code | Body mesh sync, IK, rendering |
725
690
 
726
691
  ## Roadmap
727
692
 
package/dist/index.d.ts CHANGED
@@ -491,7 +491,7 @@ interface JointStateResult {
491
491
  /**
492
492
  * Hook to access the MuJoCo WASM module.
493
493
  */
494
- declare function useMujoco(): MujocoContextValue;
494
+ declare function useMujocoWasm(): MujocoContextValue;
495
495
  interface MujocoProviderProps {
496
496
  wasmUrl?: string;
497
497
  /** Timeout in ms for WASM module load. Default: 30000. */
@@ -573,7 +573,7 @@ interface MujocoSimContextValue {
573
573
  resetCallbacks: React.RefObject<Set<() => void>>;
574
574
  status: 'loading' | 'ready' | 'error';
575
575
  }
576
- declare function useMujocoSim(): MujocoSimContextValue;
576
+ declare function useMujoco(): MujocoSimContextValue;
577
577
  declare function useBeforePhysicsStep(callback: PhysicsStepCallback): void;
578
578
  declare function useAfterPhysicsStep(callback: PhysicsStepCallback): void;
579
579
  interface MujocoSimProviderProps {
@@ -666,7 +666,7 @@ type ControllerComponent<TConfig> = React.FC<{
666
666
  * Factory that produces a typed controller component.
667
667
  *
668
668
  * Controllers are React components that plug into the MuJoCo simulation tree.
669
- * Inside `Impl`, use any hooks (`useMujocoSim`, `useBeforePhysicsStep`, etc.)
669
+ * Inside `Impl`, use any hooks (`useMujoco`, `useBeforePhysicsStep`, etc.)
670
670
  * to interact with the physics engine.
671
671
  *
672
672
  * @example
@@ -718,12 +718,6 @@ declare function useIk(options: {
718
718
  optional: true;
719
719
  }): IkContextValue | null;
720
720
 
721
- /**
722
- * SceneRenderer — creates and syncs MuJoCo body meshes every frame.
723
- * Accepts standard R3F group props (position, rotation, scale, visible, etc.).
724
- */
725
- declare function SceneRenderer(props: Omit<ThreeElements['group'], 'ref'>): react_jsx_runtime.JSX.Element;
726
-
727
721
  /**
728
722
  * IkGizmo — drei PivotControls that tracks a MuJoCo site.
729
723
  *
@@ -1160,4 +1154,4 @@ interface CameraAnimationAPI {
1160
1154
  */
1161
1155
  declare function useCameraAnimation(): CameraAnimationAPI;
1162
1156
 
1163
- 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, SceneRenderer, 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, useMujocoSim, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder };
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 };