mujoco-react 0.2.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 +206 -42
- package/dist/index.d.ts +175 -95
- package/dist/index.js +1137 -771
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ContactMarkers.tsx +12 -19
- package/src/components/Debug.tsx +168 -33
- package/src/components/DragInteraction.tsx +1 -1
- package/src/components/IkController.tsx +262 -0
- package/src/components/IkGizmo.tsx +17 -25
- package/src/components/SceneLights.tsx +2 -112
- package/src/components/SceneRenderer.tsx +8 -6
- package/src/components/SelectionHighlight.tsx +2 -49
- package/src/components/TendonRenderer.tsx +90 -26
- package/src/components/TrajectoryPlayer.tsx +14 -10
- package/src/core/IkContext.tsx +40 -0
- package/src/core/MujocoProvider.tsx +12 -4
- package/src/core/MujocoSimProvider.tsx +55 -331
- package/src/core/SceneLoader.ts +44 -11
- package/src/core/createController.tsx +91 -0
- package/src/hooks/useCameraAnimation.ts +102 -0
- package/src/hooks/useContacts.ts +52 -22
- package/src/hooks/useJointState.ts +18 -2
- package/src/hooks/useSceneLights.ts +117 -0
- package/src/hooks/useSelectionHighlight.ts +65 -0
- package/src/index.ts +16 -1
- package/src/types.ts +59 -22
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mujoco-react
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
<
|
|
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>
|
|
60
|
-
<MujocoCanvas config={...}>
|
|
61
|
-
<OrbitControls />
|
|
62
|
-
<SceneRenderer />
|
|
63
|
-
<
|
|
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
|
+
<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
|
|
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/
|
|
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?` |
|
|
250
|
+
| `siteName` | `string?` | IkController's site | MuJoCo site to track |
|
|
186
251
|
| `scale` | `number?` | `0.18` | Gizmo handle scale |
|
|
187
|
-
| `onDrag` | `(pos, quat) => void` |
|
|
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
|
-
| `
|
|
201
|
-
| `color` | `string?` | `'
|
|
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()
|
|
461
|
+
// video.start(), video.stop() -> returns Blob
|
|
371
462
|
```
|
|
372
463
|
|
|
373
464
|
### `useCtrlNoise(config)`
|
|
@@ -390,6 +481,22 @@ 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
502
|
The full API object available via `ref` or `useMujocoSim().api`:
|
|
@@ -458,31 +565,88 @@ The full API object available via `ref` or `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
|
-
| `
|
|
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
|
-
|
|
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,
|
|
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)`
|
|
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`
|
|
506
|
-
- `ten_rgba` / `ten_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
|
|