mujoco-react 0.1.0 → 0.3.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,27 +25,26 @@ 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
 
33
31
  function App() {
34
- const apiRef = useRef<MujocoSimAPI | null>(null);
32
+ const apiRef = useRef<MujocoSimAPI>(null);
35
33
 
36
34
  return (
37
35
  <MujocoProvider>
38
36
  <MujocoCanvas
37
+ ref={apiRef}
39
38
  config={config}
40
- onReady={(api) => { apiRef.current = api; }}
41
39
  camera={{ position: [2, -1.5, 2.5], up: [0, 0, 1], fov: 45 }}
42
40
  shadows
43
41
  style={{ width: '100%', height: '100vh' }}
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>
@@ -56,19 +56,87 @@ function App() {
56
56
  ## Architecture
57
57
 
58
58
  ```
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
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
+ <IkController config={..}> <- Opt-in controller plugin
64
+ <IkGizmo /> <- PivotControls-based IK handle
65
+ </IkController>
66
+ <YourController /> <- Bring your own controller
67
+ <YourLights /> <- You compose your own scene
65
68
  <YourGrid />
66
- <YourCustomLogic /> ← Your hooks + components
67
69
  </MujocoCanvas>
68
70
  </MujocoProvider>
69
71
  ```
70
72
 
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.
73
+ 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.
74
+
75
+ ## Controller Plugins
76
+
77
+ Controllers are opt-in plugins that compose library hooks. The library ships `IkController` built with the `createController` factory, but the real power is building your own.
78
+
79
+ ### `<IkController>`
80
+
81
+ The built-in IK controller. Wraps children with IK context — `<IkGizmo>` must be a descendant.
82
+
83
+ ```tsx
84
+ <IkController config={{ siteName: 'tcp', numJoints: 7 }}>
85
+ <IkGizmo />
86
+ </IkController>
87
+ ```
88
+
89
+ | Config | Type | Default | Description |
90
+ |--------|------|---------|-------------|
91
+ | `siteName` | `string` | **required** | MuJoCo site to track |
92
+ | `numJoints` | `number` | **required** | Number of joints for IK |
93
+ | `ikSolveFn` | `IKSolveFn` | built-in DLS | Custom solver function |
94
+ | `damping` | `number` | `0.01` | DLS damping |
95
+ | `maxIterations` | `number` | `50` | Max solver iterations |
96
+
97
+ ### `useIk()`
98
+
99
+ Access IK state from inside `<IkController>`:
100
+
101
+ ```tsx
102
+ const { setIkEnabled, moveTarget, solveIK } = useIk();
103
+ ```
104
+
105
+ Pass `{ optional: true }` for components that may or may not be inside an `<IkController>`:
106
+
107
+ ```tsx
108
+ const ikCtx = useIk({ optional: true });
109
+ if (ikCtx?.ikEnabledRef.current) {
110
+ ikCtx.setIkEnabled(false);
111
+ }
112
+ ```
113
+
114
+ ### `createController<TConfig>(options, Impl)`
115
+
116
+ Build your own controller plugin with the typed factory:
117
+
118
+ ```tsx
119
+ import { createController, useBeforePhysicsStep, useMujocoSim } from 'mujoco-react';
120
+
121
+ interface MyConfig {
122
+ gain: number;
123
+ targetJoint: string;
124
+ }
125
+
126
+ function MyControllerImpl({ config }: { config: MyConfig; children?: React.ReactNode }) {
127
+ useBeforePhysicsStep((_model, data) => {
128
+ data.ctrl[0] = config.gain * Math.sin(data.time);
129
+ });
130
+ return null;
131
+ }
132
+
133
+ export const MyController = createController<MyConfig>(
134
+ { name: 'MyController', defaultConfig: { gain: 1.0 } },
135
+ MyControllerImpl,
136
+ );
137
+
138
+ // Usage: <MyController config={{ gain: 2.0, targetJoint: 'shoulder' }} />
139
+ ```
72
140
 
73
141
  ## Loading Models
74
142
 
@@ -85,7 +153,7 @@ const franka: SceneConfig = {
85
153
  const so101: SceneConfig = {
86
154
  robotId: 'so101',
87
155
  sceneFile: 'SO101.xml',
88
- baseUrl: 'https://raw.githubusercontent.com/Vector-Wangel/MuJoCo-GS-Web/main/assets/robots/xlerobot/',
156
+ baseUrl: 'https://raw.githubusercontent.com/your-org/your-repo/main/models/',
89
157
  };
90
158
 
91
159
  // Self-hosted
@@ -106,9 +174,6 @@ interface SceneConfig {
106
174
  sceneFile: string; // Entry XML file, e.g. 'scene.xml'
107
175
  baseUrl?: string; // Base URL for fetching model files
108
176
  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
177
  homeJoints?: number[]; // Initial joint positions
113
178
  xmlPatches?: XmlPatch[]; // Patches applied to XML files during loading
114
179
  onReset?: (model, data) => void; // Called during reset after mj_resetData
@@ -178,13 +243,13 @@ Syncs MuJoCo bodies to Three.js meshes every frame. Must be inside `<MujocoCanva
178
243
 
179
244
  ### `<IkGizmo />`
180
245
 
181
- drei PivotControls gizmo that tracks a MuJoCo site and drives IK on drag.
246
+ drei PivotControls gizmo that tracks a MuJoCo site and drives IK on drag. Must be inside `<IkController>`.
182
247
 
183
248
  | Prop | Type | Default | Description |
184
249
  |------|------|---------|-------------|
185
- | `siteName` | `string?` | config.tcpSiteName | MuJoCo site to track |
250
+ | `siteName` | `string?` | IkController's site | MuJoCo site to track |
186
251
  | `scale` | `number?` | `0.18` | Gizmo handle scale |
187
- | `onDrag` | `(pos, quat) => void` | | Custom drag handler (disables auto-IK) |
252
+ | `onDrag` | `(pos, quat) => void` | -- | Custom drag handler (disables auto-IK) |
188
253
 
189
254
  ### `<DragInteraction />`
190
255
 
@@ -197,12 +262,13 @@ InstancedMesh showing MuJoCo contact points for debugging.
197
262
  | Prop | Type | Default | Description |
198
263
  |------|------|---------|-------------|
199
264
  | `maxContacts` | `number?` | `100` | Max contacts to display |
200
- | `size` | `number?` | `0.005` | Marker sphere radius |
201
- | `color` | `string?` | `'red'` | Marker color |
265
+ | `radius` | `number?` | `0.005` | Marker sphere radius |
266
+ | `color` | `string?` | `'#4f46e5'` | Marker color |
267
+ | `visible` | `boolean?` | `true` | Toggle visibility |
202
268
 
203
269
  ### `<SceneLights />`
204
270
 
205
- Auto-creates Three.js lights from MJCF `<light>` elements.
271
+ Auto-creates Three.js lights from MJCF `<light>` elements. Also available as `useSceneLights(intensity?)` hook.
206
272
 
207
273
  ### `<Debug />`
208
274
 
@@ -215,6 +281,12 @@ Visualization overlays:
215
281
  | `showJoints` | `boolean?` | `false` | Joint axes |
216
282
  | `showContacts` | `boolean?` | `false` | Contact force vectors |
217
283
  | `showCOM` | `boolean?` | `false` | Center of mass markers |
284
+ | `showInertia` | `boolean?` | `false` | Inertia ellipsoids |
285
+ | `showTendons` | `boolean?` | `false` | Tendon paths |
286
+ | `geomColor` | `string?` | `'#00ff00'` | Color for wireframe geoms |
287
+ | `siteColor` | `string?` | `'#ff00ff'` | Color for site markers |
288
+ | `contactColor` | `string?` | `'#ff4444'` | Color for contact force arrows |
289
+ | `comColor` | `string?` | `'#ff0000'` | Color for COM markers |
218
290
 
219
291
  ### `<TendonRenderer />`
220
292
 
@@ -238,7 +310,7 @@ Component wrapper for contact events:
238
310
 
239
311
  ### `<SelectionHighlight />`
240
312
 
241
- Emissive highlight on selected body meshes.
313
+ Emissive highlight on selected body meshes. Also available as `useSelectionHighlight(bodyId, options?)` hook.
242
314
 
243
315
  ### `<TrajectoryPlayer />`
244
316
 
@@ -268,6 +340,25 @@ useBeforePhysicsStep((model, data) => {
268
340
 
269
341
  Run logic **after** `mj_step` each frame. Read results, compute rewards, log telemetry.
270
342
 
343
+ ### `useIk()` / `useIk({ optional: true })`
344
+
345
+ Access IK controller state. `useIk()` throws if not inside `<IkController>`. Pass `{ optional: true }` to get `null` instead.
346
+
347
+ ### `useCameraAnimation()`
348
+
349
+ Standalone camera animation hook:
350
+
351
+ ```tsx
352
+ const { getCameraState, moveCameraTo } = useCameraAnimation();
353
+
354
+ // Animate camera over 1 second
355
+ await moveCameraTo(
356
+ new THREE.Vector3(3, 0, 2),
357
+ new THREE.Vector3(0, 0, 0.5),
358
+ 1000
359
+ );
360
+ ```
361
+
271
362
  ### `useSensor(name)` / `useSensors()`
272
363
 
273
364
  Read sensor values by name (ref-based, no re-renders):
@@ -367,7 +458,7 @@ Record the canvas as video:
367
458
 
368
459
  ```tsx
369
460
  const video = useVideoRecorder({ fps: 30, mimeType: 'video/webm' });
370
- // video.start(), video.stop() returns Blob
461
+ // video.start(), video.stop() -> returns Blob
371
462
  ```
372
463
 
373
464
  ### `useCtrlNoise(config)`
@@ -390,9 +481,25 @@ Returns actuator metadata for building control UIs.
390
481
 
391
482
  Ref-based site position/quaternion tracking.
392
483
 
484
+ ### `useSelectionHighlight(bodyId, options?)`
485
+
486
+ Hook form of `<SelectionHighlight>`. Apply emissive highlights imperatively:
487
+
488
+ ```tsx
489
+ useSelectionHighlight(selectedBodyId, { color: '#00ff00', emissiveIntensity: 0.5 });
490
+ ```
491
+
492
+ ### `useSceneLights(intensity?)`
493
+
494
+ Hook form of `<SceneLights>`. Create Three.js lights from MJCF definitions imperatively:
495
+
496
+ ```tsx
497
+ useSceneLights(1.5);
498
+ ```
499
+
393
500
  ## MujocoSimAPI
394
501
 
395
- The full API object returned by `onReady` and available via `useMujocoSim().api`:
502
+ The full API object available via `ref` or `useMujocoSim().api`:
396
503
 
397
504
  ### Simulation Control
398
505
 
@@ -458,31 +565,88 @@ The full API object returned by `onReady` and available via `useMujocoSim().api`
458
565
  |--------|-------------|
459
566
  | `raycast(origin, direction, maxDist?)` | Physics raycast via `mj_ray` |
460
567
  | `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 |
568
+ | `getCanvasSnapshot(w?, h?, mime?)` | Base64 screenshot |
472
569
 
473
570
  ### Scene Management
474
571
 
475
572
  | Method | Description |
476
573
  |--------|-------------|
477
574
  | `loadScene(newConfig)` | Runtime model swap |
478
- | `getCanvasSnapshot(w?, h?, mime?)` | Base64 screenshot |
575
+
576
+ ## Guides
577
+
578
+ ### Building Controllers
579
+
580
+ Controllers are thin components that compose library hooks. The simplest is a `useKeyboardTeleop` call:
581
+
582
+ ```tsx
583
+ function FrankaController() {
584
+ useKeyboardTeleop({
585
+ bindings: { v: { actuator: 'gripper', toggle: [0, 255] } },
586
+ });
587
+ return null;
588
+ }
589
+ ```
590
+
591
+ For custom control (IK solvers, velocity control), use `useBeforePhysicsStep`:
592
+
593
+ ```tsx
594
+ function MyController() {
595
+ const keys = useRef<Record<string, boolean>>({});
596
+ // ... keyboard listeners ...
597
+
598
+ useBeforePhysicsStep((_model, data) => {
599
+ if (keys.current['KeyW']) data.ctrl[0] += 0.01;
600
+ });
601
+ return null;
602
+ }
603
+ ```
604
+
605
+ For reusable controller plugins, use `createController<TConfig>()` to build typed components with config merging and metadata.
606
+
607
+ See [Building Controllers](https://mujoco-react.mintlify.app/guides/building-controllers) for config-driven patterns, IK gizmo coexistence, and multi-arm support.
608
+
609
+ ### Graspable Objects
610
+
611
+ Objects need specific MuJoCo contact parameters to be picked up by grippers:
612
+
613
+ ```tsx
614
+ sceneObjects: [{
615
+ name: 'cube',
616
+ type: 'box',
617
+ size: [0.025, 0.025, 0.025],
618
+ position: [0.4, 0, 0.025],
619
+ rgba: [0.9, 0.2, 0.15, 1],
620
+ mass: 0.05,
621
+ freejoint: true,
622
+ friction: '1.5 0.3 0.1', // high sliding friction
623
+ solref: '0.01 1', // stiff contact solver
624
+ solimp: '0.95 0.99 0.001 0.5 2', // tight impedance
625
+ condim: 4, // elliptic friction cone
626
+ }]
627
+ ```
628
+
629
+ 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.
630
+
631
+ ### Click-to-Select
632
+
633
+ Combine R3F raycasting with `<SelectionHighlight />` for body selection:
634
+
635
+ ```tsx
636
+ function ClickSelectOverlay() {
637
+ const selectedBodyId = useClickSelect(); // your raycasting hook
638
+ return <SelectionHighlight bodyId={selectedBodyId} />;
639
+ }
640
+ ```
641
+
642
+ See [Click-to-Select](https://mujoco-react.mintlify.app/guides/click-to-select) for the full implementation.
479
643
 
480
644
  ## useFrame Priority
481
645
 
482
646
  | Priority | Owner | Purpose |
483
647
  |----------|-------|---------|
484
- | -1 | MujocoSimProvider | beforeStep, IK, mj_step, afterStep |
485
- | 0 (default) | SceneRenderer, your code | Body mesh sync, rendering |
648
+ | -1 | MujocoSimProvider | beforeStep, mj_step, afterStep |
649
+ | 0 (default) | SceneRenderer, IkController, your code | Body mesh sync, IK, rendering |
486
650
 
487
651
  ## Roadmap
488
652
 
@@ -490,7 +654,7 @@ Features planned but not yet implemented:
490
654
 
491
655
  | Feature | Priority | Description |
492
656
  |---------|----------|-------------|
493
- | **User-uploaded model loading** | P2 | `loadFromFiles(FileList)` detect meshdir, write to VFS |
657
+ | **User-uploaded model loading** | P2 | `loadFromFiles(FileList)` -- detect meshdir, write to VFS |
494
658
  | **URDF loading** | P2 | Load URDF models via MuJoCo's built-in URDF compiler |
495
659
  | **XML mutation / recompile** | P1 | `addBody()`, `removeBody()`, `recompile()` for runtime XML editing |
496
660
  | **Observation builder utilities** | P2 | Helpers for projected gravity, joint positions/velocities for RL |
@@ -502,8 +666,8 @@ Features planned but not yet implemented:
502
666
 
503
667
  These MuJoCo features are not yet exposed in the WASM binding:
504
668
 
505
- - `flex_faceadr` / `flex_facenum` / `flex_face` FlexRenderer renders vertices without face indices
506
- - `ten_rgba` / `ten_width` TendonRenderer uses default color/width
669
+ - `flex_faceadr` / `flex_facenum` / `flex_face` -- FlexRenderer renders vertices without face indices
670
+ - `ten_rgba` / `ten_width` -- TendonRenderer uses default color/width
507
671
 
508
672
  ## License
509
673