mujoco-react 0.2.0 → 1.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,6 +1,6 @@
1
1
  # mujoco-react
2
2
 
3
- Composable [React Three Fiber](https://docs.pmnd.rs/react-three-fiber) building blocks for [MuJoCo](https://mujoco.org/) WASM simulations. Works with **any robot, any scene** — you provide the MJCF model, the library handles physics, rendering, and IK.
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**.
4
4
 
5
5
 
6
6
  ## Install
@@ -16,6 +16,7 @@ import {
16
16
  MujocoProvider,
17
17
  MujocoCanvas,
18
18
  SceneRenderer,
19
+ IkController,
19
20
  IkGizmo,
20
21
  } from 'mujoco-react';
21
22
  import type { SceneConfig, MujocoSimAPI } from 'mujoco-react';
@@ -24,9 +25,6 @@ import { OrbitControls } from '@react-three/drei';
24
25
  const config: SceneConfig = {
25
26
  robotId: 'franka_emika_panda',
26
27
  sceneFile: 'scene.xml',
27
- numArmJoints: 7,
28
- tcpSiteName: 'tcp',
29
- gripperActuatorName: 'gripper',
30
28
  homeJoints: [1.707, -1.754, 0.003, -2.702, 0.003, 0.951, 2.490],
31
29
  };
32
30
 
@@ -44,7 +42,9 @@ function App() {
44
42
  >
45
43
  <OrbitControls enableDamping makeDefault />
46
44
  <SceneRenderer />
47
- <IkGizmo />
45
+ <IkController config={{ siteName: 'tcp', numJoints: 7 }}>
46
+ <IkGizmo />
47
+ </IkController>
48
48
  <ambientLight intensity={0.7} />
49
49
  <directionalLight position={[1, 2, 5]} intensity={1.2} castShadow />
50
50
  </MujocoCanvas>
@@ -55,20 +55,154 @@ function App() {
55
55
 
56
56
  ## Architecture
57
57
 
58
+ Two ways to set up your scene:
59
+
60
+ ### `<MujocoCanvas>` — Quick Start
61
+
62
+ Wraps R3F `<Canvas>` for you. Fastest path to a working scene:
63
+
58
64
  ```
59
- <MujocoProvider> WASM module lifecycle
60
- <MujocoCanvas config={...}> Thin R3F Canvas wrapper + physics context
61
- <OrbitControls /> You add your own controls
62
- <SceneRenderer /> ← Syncs MuJoCo bodies to Three.js meshes
63
- <IkGizmo /> ← PivotControls-based IK handle
64
- <YourLights /> ← You compose your own scene
65
- <YourGrid />
66
- <YourCustomLogic /> Your hooks + components
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
67
73
  </MujocoCanvas>
68
74
  </MujocoProvider>
69
75
  ```
70
76
 
71
- The library provides **only MuJoCo engine concerns**: WASM lifecycle, physics stepping, body rendering, IK solving. Everything else (lighting, grid, markers, game logic) is composed by the consumer as R3F children.
77
+ ### `<MujocoPhysics>` Bring Your Own Canvas
78
+
79
+ Use inside your own `<Canvas>` for full control over gl settings, post-processing, and R3F context composition:
80
+
81
+ ```
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
+
96
+ ## Bring Your Own Controller
97
+
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.
99
+
100
+ ```tsx
101
+ import { useBeforePhysicsStep } from 'mujoco-react';
102
+
103
+ function MyController() {
104
+ useBeforePhysicsStep((_model, data) => {
105
+ data.ctrl[0] = Math.sin(data.time); // sine wave on actuator 0
106
+ data.ctrl[1] = data.sensordata[0] * -0.5; // feedback from a sensor
107
+ });
108
+ return null;
109
+ }
110
+
111
+ // Drop it in:
112
+ <MujocoCanvas config={config}>
113
+ <SceneRenderer />
114
+ <MyController />
115
+ </MujocoCanvas>
116
+ ```
117
+
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:
123
+
124
+ ```tsx
125
+ import type { IKSolveFn } from 'mujoco-react';
126
+
127
+ const myIK: IKSolveFn = (pos, quat, currentQ) => {
128
+ return myAnalyticalSolver(pos, currentQ); // return joint angles or null
129
+ };
130
+
131
+ <IkController config={{ siteName: 'tcp', numJoints: 7, ikSolveFn: myIK }}>
132
+ <IkGizmo />
133
+ </IkController>
134
+ ```
135
+
136
+ Or skip `<IkController>` entirely and solve IK yourself inside `useBeforePhysicsStep`:
137
+
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
+ }
148
+ ```
149
+
150
+ ### `createController<TConfig>()` Factory
151
+
152
+ For reusable controller plugins with typed config and default merging:
153
+
154
+ ```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
+ }
168
+
169
+ export const MyController = createController<MyConfig>(
170
+ { name: 'MyController', defaultConfig: { gain: 1.0 } },
171
+ MyControllerImpl,
172
+ );
173
+
174
+ // Usage: <MyController config={{ gain: 2.0, targetJoint: 'shoulder' }} />
175
+ ```
176
+
177
+ ### Built-in `<IkController>`
178
+
179
+ The library ships one controller out of the box — an IK gizmo you can drop in for interactive end-effector control:
180
+
181
+ ```tsx
182
+ <IkController config={{ siteName: 'tcp', numJoints: 7 }}>
183
+ <IkGizmo />
184
+ </IkController>
185
+ ```
186
+
187
+ | Config | Type | Default | Description |
188
+ |--------|------|---------|-------------|
189
+ | `siteName` | `string` | **required** | MuJoCo site to track |
190
+ | `numJoints` | `number` | **required** | Number of joints for IK |
191
+ | `ikSolveFn` | `IKSolveFn` | built-in DLS | Custom solver function |
192
+ | `damping` | `number` | `0.01` | DLS damping |
193
+ | `maxIterations` | `number` | `50` | Max solver iterations |
194
+
195
+ Access IK state from inside `<IkController>` with `useIk()`:
196
+
197
+ ```tsx
198
+ const { setIkEnabled, moveTarget, solveIK } = useIk();
199
+ ```
200
+
201
+ Pass `{ optional: true }` for components that may or may not be inside an `<IkController>`:
202
+
203
+ ```tsx
204
+ const ikCtx = useIk({ optional: true });
205
+ ```
72
206
 
73
207
  ## Loading Models
74
208
 
@@ -85,7 +219,7 @@ const franka: SceneConfig = {
85
219
  const so101: SceneConfig = {
86
220
  robotId: 'so101',
87
221
  sceneFile: 'SO101.xml',
88
- baseUrl: 'https://raw.githubusercontent.com/Vector-Wangel/MuJoCo-GS-Web/main/assets/robots/xlerobot/',
222
+ baseUrl: 'https://raw.githubusercontent.com/your-org/your-repo/main/models/',
89
223
  };
90
224
 
91
225
  // Self-hosted
@@ -106,9 +240,6 @@ interface SceneConfig {
106
240
  sceneFile: string; // Entry XML file, e.g. 'scene.xml'
107
241
  baseUrl?: string; // Base URL for fetching model files
108
242
  sceneObjects?: SceneObject[]; // Objects injected into scene XML at load time
109
- tcpSiteName?: string; // MuJoCo site for IK. Default: 'tcp'
110
- gripperActuatorName?: string; // Gripper actuator name. Default: 'gripper'
111
- numArmJoints?: number; // Number of arm joints for IK. Default: 7
112
243
  homeJoints?: number[]; // Initial joint positions
113
244
  xmlPatches?: XmlPatch[]; // Patches applied to XML files during loading
114
245
  onReset?: (model, data) => void; // Called during reset after mj_resetData
@@ -168,28 +299,64 @@ Thin wrapper around R3F `<Canvas>`. Accepts all R3F Canvas props plus:
168
299
  | `substeps` | `number` | mj_step calls per frame |
169
300
  | `paused` | `boolean` | Declarative pause |
170
301
  | `speed` | `number` | Simulation speed multiplier |
171
- | `interpolate` | `boolean` | Interpolate body transforms between physics frames |
172
- | `gravityCompensation` | `boolean` | Auto-apply gravity compensation |
173
- | `mjcfLights` | `boolean` | Auto-create lights from MJCF model |
302
+
303
+ ### `<MujocoPhysics>`
304
+
305
+ Physics provider for use inside your own R3F `<Canvas>`. Same physics props as `<MujocoCanvas>` without the Canvas wrapper. Accepts a `ref` for the `MujocoSimAPI`.
306
+
307
+ ```tsx
308
+ <MujocoProvider>
309
+ <Canvas shadows camera={{ position: [2, 2, 2] }}>
310
+ <MujocoPhysics ref={apiRef} config={config} paused={paused}>
311
+ <SceneRenderer />
312
+ <MyController />
313
+ </MujocoPhysics>
314
+ <OrbitControls />
315
+ </Canvas>
316
+ </MujocoProvider>
317
+ ```
318
+
319
+ | Prop | Type | Description |
320
+ |------|------|-------------|
321
+ | `config` | `SceneConfig` | **Required.** Scene/robot configuration |
322
+ | `onReady` | `(api: MujocoSimAPI) => void` | Fires when model is loaded |
323
+ | `onError` | `(error: Error) => void` | Fires on scene load failure |
324
+ | `onStep` | `(time: number) => void` | Called each physics step |
325
+ | `onSelection` | `(bodyId: number, name: string) => void` | Called on double-click |
326
+ | `gravity` | `[number, number, number]` | Override model gravity |
327
+ | `timestep` | `number` | Override model.opt.timestep |
328
+ | `substeps` | `number` | mj_step calls per frame |
329
+ | `paused` | `boolean` | Declarative pause |
330
+ | `speed` | `number` | Simulation speed multiplier |
174
331
 
175
332
  ### `<SceneRenderer />`
176
333
 
177
- Syncs MuJoCo bodies to Three.js meshes every frame. Must be inside `<MujocoCanvas>`.
334
+ Syncs MuJoCo bodies to Three.js meshes every frame. Must be inside `<MujocoCanvas>` or `<MujocoPhysics>`.
178
335
 
179
336
  ### `<IkGizmo />`
180
337
 
181
- drei PivotControls gizmo that tracks a MuJoCo site and drives IK on drag.
338
+ drei PivotControls gizmo that tracks a MuJoCo site and drives IK on drag. Must be inside `<IkController>`.
182
339
 
183
340
  | Prop | Type | Default | Description |
184
341
  |------|------|---------|-------------|
185
- | `siteName` | `string?` | config.tcpSiteName | MuJoCo site to track |
342
+ | `siteName` | `string?` | IkController's site | MuJoCo site to track |
186
343
  | `scale` | `number?` | `0.18` | Gizmo handle scale |
187
- | `onDrag` | `(pos, quat) => void` | | Custom drag handler (disables auto-IK) |
344
+ | `onDrag` | `(pos, quat) => void` | -- | Custom drag handler (disables auto-IK) |
188
345
 
189
346
  ### `<DragInteraction />`
190
347
 
191
348
  Click-drag to apply spring forces to bodies. Raycasts to find bodies, applies `F = (mouseWorld - grabWorld) * body_mass * stiffness` via `mj_applyFT`.
192
349
 
350
+ ### R3F Group Props
351
+
352
+ All visual components (`SceneRenderer`, `DragInteraction`, `ContactMarkers`, `Debug`, `TendonRenderer`, `FlexRenderer`) accept standard R3F group props — `position`, `rotation`, `scale`, `visible`, etc.
353
+
354
+ ```tsx
355
+ <SceneRenderer position={[0, 0, 1]} />
356
+ <ContactMarkers visible={showContacts} />
357
+ <Debug showJoints scale={0.5} />
358
+ ```
359
+
193
360
  ### `<ContactMarkers />`
194
361
 
195
362
  InstancedMesh showing MuJoCo contact points for debugging.
@@ -197,12 +364,13 @@ InstancedMesh showing MuJoCo contact points for debugging.
197
364
  | Prop | Type | Default | Description |
198
365
  |------|------|---------|-------------|
199
366
  | `maxContacts` | `number?` | `100` | Max contacts to display |
200
- | `size` | `number?` | `0.005` | Marker sphere radius |
201
- | `color` | `string?` | `'red'` | Marker color |
367
+ | `radius` | `number?` | `0.005` | Marker sphere radius |
368
+ | `color` | `string?` | `'#4f46e5'` | Marker color |
369
+ | `visible` | `boolean?` | `true` | Toggle visibility |
202
370
 
203
371
  ### `<SceneLights />`
204
372
 
205
- Auto-creates Three.js lights from MJCF `<light>` elements.
373
+ Auto-creates Three.js lights from MJCF `<light>` elements. Also available as `useSceneLights(intensity?)` hook.
206
374
 
207
375
  ### `<Debug />`
208
376
 
@@ -215,6 +383,12 @@ Visualization overlays:
215
383
  | `showJoints` | `boolean?` | `false` | Joint axes |
216
384
  | `showContacts` | `boolean?` | `false` | Contact force vectors |
217
385
  | `showCOM` | `boolean?` | `false` | Center of mass markers |
386
+ | `showInertia` | `boolean?` | `false` | Inertia ellipsoids |
387
+ | `showTendons` | `boolean?` | `false` | Tendon paths |
388
+ | `geomColor` | `string?` | `'#00ff00'` | Color for wireframe geoms |
389
+ | `siteColor` | `string?` | `'#ff00ff'` | Color for site markers |
390
+ | `contactColor` | `string?` | `'#ff4444'` | Color for contact force arrows |
391
+ | `comColor` | `string?` | `'#ff0000'` | Color for COM markers |
218
392
 
219
393
  ### `<TendonRenderer />`
220
394
 
@@ -238,7 +412,7 @@ Component wrapper for contact events:
238
412
 
239
413
  ### `<SelectionHighlight />`
240
414
 
241
- Emissive highlight on selected body meshes.
415
+ Emissive highlight on selected body meshes. Also available as `useSelectionHighlight(bodyId, options?)` hook.
242
416
 
243
417
  ### `<TrajectoryPlayer />`
244
418
 
@@ -268,6 +442,25 @@ useBeforePhysicsStep((model, data) => {
268
442
 
269
443
  Run logic **after** `mj_step` each frame. Read results, compute rewards, log telemetry.
270
444
 
445
+ ### `useIk()` / `useIk({ optional: true })`
446
+
447
+ Access IK controller state. `useIk()` throws if not inside `<IkController>`. Pass `{ optional: true }` to get `null` instead.
448
+
449
+ ### `useCameraAnimation()`
450
+
451
+ Standalone camera animation hook:
452
+
453
+ ```tsx
454
+ const { getCameraState, moveCameraTo } = useCameraAnimation();
455
+
456
+ // Animate camera over 1 second
457
+ await moveCameraTo(
458
+ new THREE.Vector3(3, 0, 2),
459
+ new THREE.Vector3(0, 0, 0.5),
460
+ 1000
461
+ );
462
+ ```
463
+
271
464
  ### `useSensor(name)` / `useSensors()`
272
465
 
273
466
  Read sensor values by name (ref-based, no re-renders):
@@ -367,7 +560,7 @@ Record the canvas as video:
367
560
 
368
561
  ```tsx
369
562
  const video = useVideoRecorder({ fps: 30, mimeType: 'video/webm' });
370
- // video.start(), video.stop() returns Blob
563
+ // video.start(), video.stop() -> returns Blob
371
564
  ```
372
565
 
373
566
  ### `useCtrlNoise(config)`
@@ -390,6 +583,22 @@ Returns actuator metadata for building control UIs.
390
583
 
391
584
  Ref-based site position/quaternion tracking.
392
585
 
586
+ ### `useSelectionHighlight(bodyId, options?)`
587
+
588
+ Hook form of `<SelectionHighlight>`. Apply emissive highlights imperatively:
589
+
590
+ ```tsx
591
+ useSelectionHighlight(selectedBodyId, { color: '#00ff00', emissiveIntensity: 0.5 });
592
+ ```
593
+
594
+ ### `useSceneLights(intensity?)`
595
+
596
+ Hook form of `<SceneLights>`. Create Three.js lights from MJCF definitions imperatively:
597
+
598
+ ```tsx
599
+ useSceneLights(1.5);
600
+ ```
601
+
393
602
  ## MujocoSimAPI
394
603
 
395
604
  The full API object available via `ref` or `useMujocoSim().api`:
@@ -458,31 +667,61 @@ The full API object available via `ref` or `useMujocoSim().api`:
458
667
  |--------|-------------|
459
668
  | `raycast(origin, direction, maxDist?)` | Physics raycast via `mj_ray` |
460
669
  | `project2DTo3D(x, y, camPos, lookAt)` | Screen-to-world raycast (returns bodyId + geomId) |
461
- | `getCameraState()` | Camera position and orbit target |
462
- | `moveCameraTo(pos, target, ms)` | Smooth camera animation |
463
-
464
- ### IK Control
465
-
466
- | Method | Description |
467
- |--------|-------------|
468
- | `setIkEnabled(enabled)` | Enable/disable IK tracking |
469
- | `moveTarget(pos, duration?)` | Move IK target with optional animation |
470
- | `syncTargetToSite()` | Snap IK target to current TCP position |
471
- | `solveIK(pos, quat, currentQ)` | Solve IK for a target pose |
670
+ | `getCanvasSnapshot(w?, h?, mime?)` | Base64 screenshot |
472
671
 
473
672
  ### Scene Management
474
673
 
475
674
  | Method | Description |
476
675
  |--------|-------------|
477
676
  | `loadScene(newConfig)` | Runtime model swap |
478
- | `getCanvasSnapshot(w?, h?, mime?)` | Base64 screenshot |
677
+
678
+ ## Guides
679
+
680
+ ### Building Controllers
681
+
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
685
+
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
+ ```
703
+
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.
705
+
706
+ ### Click-to-Select
707
+
708
+ Combine R3F raycasting with `<SelectionHighlight />` for body selection:
709
+
710
+ ```tsx
711
+ function ClickSelectOverlay() {
712
+ const selectedBodyId = useClickSelect(); // your raycasting hook
713
+ return <SelectionHighlight bodyId={selectedBodyId} />;
714
+ }
715
+ ```
716
+
717
+ See [Click-to-Select](https://mujoco-react.mintlify.app/guides/click-to-select) for the full implementation.
479
718
 
480
719
  ## useFrame Priority
481
720
 
482
721
  | Priority | Owner | Purpose |
483
722
  |----------|-------|---------|
484
- | -1 | MujocoSimProvider | beforeStep, IK, mj_step, afterStep |
485
- | 0 (default) | SceneRenderer, your code | Body mesh sync, rendering |
723
+ | -1 | MujocoSimProvider | beforeStep, mj_step, afterStep |
724
+ | 0 (default) | SceneRenderer, IkController, your code | Body mesh sync, IK, rendering |
486
725
 
487
726
  ## Roadmap
488
727
 
@@ -490,11 +729,11 @@ Features planned but not yet implemented:
490
729
 
491
730
  | Feature | Priority | Description |
492
731
  |---------|----------|-------------|
493
- | **User-uploaded model loading** | P2 | `loadFromFiles(FileList)` detect meshdir, write to VFS |
732
+ | **User-uploaded model loading** | P2 | `loadFromFiles(FileList)` -- detect meshdir, write to VFS |
494
733
  | **URDF loading** | P2 | Load URDF models via MuJoCo's built-in URDF compiler |
495
734
  | **XML mutation / recompile** | P1 | `addBody()`, `removeBody()`, `recompile()` for runtime XML editing |
496
735
  | **Observation builder utilities** | P2 | Helpers for projected gravity, joint positions/velocities for RL |
497
- | **Physics interpolation** | P1 | Smooth rendering between physics ticks for 120Hz+ displays |
736
+ | **Physics interpolation** | P1 | Smooth rendering between physics ticks for very high refresh displays |
498
737
  | **Instanced geom rendering** | P2 | `<InstancedGeomRenderer />` for particle/granular sims |
499
738
  | **Web Worker physics** | P2 | Run `mj_step` off main thread via SharedArrayBuffer |
500
739
 
@@ -502,8 +741,8 @@ Features planned but not yet implemented:
502
741
 
503
742
  These MuJoCo features are not yet exposed in the WASM binding:
504
743
 
505
- - `flex_faceadr` / `flex_facenum` / `flex_face` FlexRenderer renders vertices without face indices
506
- - `ten_rgba` / `ten_width` TendonRenderer uses default color/width
744
+ - `flex_faceadr` / `flex_facenum` / `flex_face` -- FlexRenderer renders vertices without face indices
745
+ - `ten_rgba` / `ten_width` -- TendonRenderer uses default color/width
507
746
 
508
747
  ## License
509
748