mujoco-react 8.4.0 → 8.4.2

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.
Files changed (2) hide show
  1. package/README.md +54 -82
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -18,113 +18,89 @@ Composable [React Three Fiber](https://docs.pmnd.rs/react-three-fiber) wrapper a
18
18
  npm install mujoco-react three @react-three/fiber @react-three/drei
19
19
  ```
20
20
 
21
- ## Quick Start
21
+ ## Load a Model
22
22
 
23
23
  ```tsx
24
- import {
25
- MujocoProvider,
26
- MujocoCanvas,
27
- useIkController,
28
- IkGizmo,
29
- } from "mujoco-react";
24
+ import { MujocoProvider, MujocoCanvas } from "mujoco-react";
30
25
  import type { SceneConfig } from "mujoco-react";
31
- import { OrbitControls } from "@react-three/drei";
32
26
 
33
- const config: SceneConfig = {
27
+ const panda: SceneConfig = {
34
28
  src: "https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/franka_emika_panda/",
35
29
  sceneFile: "scene.xml",
36
30
  homeJoints: [1.707, -1.754, 0.003, -2.702, 0.003, 0.951, 2.490],
37
31
  };
38
32
 
39
- function Scene() {
40
- const ik = useIkController({ siteName: "tcp" });
41
- return (
42
- <>
43
- <OrbitControls enableDamping makeDefault />
44
- {ik && <IkGizmo controller={ik} />}
45
- <ambientLight intensity={0.7} />
46
- <directionalLight position={[1, 2, 5]} intensity={1.2} castShadow />
47
- </>
48
- );
49
- }
50
-
51
- function App() {
33
+ export function App() {
52
34
  return (
53
35
  <MujocoProvider>
54
36
  <MujocoCanvas
55
- config={config}
37
+ config={panda}
56
38
  camera={{ position: [2, -1.5, 2.5], up: [0, 0, 1], fov: 45 }}
57
- shadows
58
39
  style={{ width: "100%", height: "100vh" }}
59
- >
60
- <Scene />
61
- </MujocoCanvas>
40
+ />
62
41
  </MujocoProvider>
63
42
  );
64
43
  }
65
44
  ```
66
45
 
67
- ## `useMujoco()`
68
-
69
- Inside `<MujocoCanvas>` or `<MujocoPhysics>`, `useMujoco()` gives you the simulation API, refs to the live model/data, and status:
46
+ ## Add Scene Tools
70
47
 
71
48
  ```tsx
72
- import { useMujoco } from "mujoco-react";
73
-
74
- function MyComponent() {
75
- const { isPending, isError, error, api, mjModelRef } = useMujoco();
49
+ import { OrbitControls } from "@react-three/drei";
50
+ import { IkGizmo, useIkController } from "mujoco-react";
76
51
 
77
- if (isPending) return <span>Loading...</span>;
78
- if (isError) return <span>Error: {error}</span>;
52
+ function PandaTools() {
53
+ const ik = useIkController({ siteName: "tcp" });
79
54
 
80
55
  return (
81
- <button onClick={() => api.reset()}>
82
- Reset ({mjModelRef.current?.nq} joints)
83
- </button>
56
+ <>
57
+ <OrbitControls makeDefault />
58
+ {ik && <IkGizmo controller={ik} />}
59
+ <ambientLight intensity={0.7} />
60
+ <directionalLight position={[1, 2, 5]} intensity={1.2} />
61
+ </>
84
62
  );
85
63
  }
86
64
  ```
87
65
 
88
- ## Writing a Controller
89
-
90
- A controller is a hook that reads sensors and writes actuators each physics step:
66
+ Use it as a child of `<MujocoCanvas>`:
91
67
 
92
68
  ```tsx
93
- import { useCtrl, useSensor, useBeforePhysicsStep } from "mujoco-react";
69
+ <MujocoCanvas config={panda}>
70
+ <PandaTools />
71
+ </MujocoCanvas>
72
+ ```
73
+
74
+ ## Write Controllers
94
75
 
95
- function useMyController(gain: number) {
96
- const shoulder = useCtrl("shoulder");
97
- const elbow = useCtrl("elbow");
98
- const force = useSensor("force_sensor");
76
+ ```tsx
77
+ import { useBeforePhysicsStep } from "mujoco-react";
99
78
 
100
- useBeforePhysicsStep(() => {
101
- shoulder.write(gain * Math.sin(Date.now() / 1000));
102
- elbow.write(force.read()[0] * -0.5);
79
+ function MyController() {
80
+ useBeforePhysicsStep((_model, data) => {
81
+ data.ctrl[0] = Math.sin(data.time);
103
82
  });
83
+
84
+ return null;
104
85
  }
105
86
  ```
106
87
 
107
- ### Applying Forces
88
+ Controllers are just React children that read sensors, write `data.ctrl`, apply forces, or call the `MujocoSimAPI` at physics-step time.
108
89
 
109
- Write directly to `xfrc_applied` in a physics callback for per-frame forces. The layout is `[torque_x, torque_y, torque_z, force_x, force_y, force_z]` per body:
90
+ ## Use the Sim API
110
91
 
111
92
  ```tsx
112
- import { useBeforePhysicsStep } from "mujoco-react";
93
+ import { useMujoco } from "mujoco-react";
113
94
 
114
- function useSpringForce(bodyId: number, target: [number, number, number], stiffness = 100) {
115
- useBeforePhysicsStep((_model, data) => {
116
- const i3 = bodyId * 3;
117
- const i6 = bodyId * 6;
118
- data.xfrc_applied[i6 + 3] = (target[0] - data.xpos[i3]) * stiffness;
119
- data.xfrc_applied[i6 + 4] = (target[1] - data.xpos[i3 + 1]) * stiffness;
120
- data.xfrc_applied[i6 + 5] = (target[2] - data.xpos[i3 + 2]) * stiffness;
121
- });
95
+ function ResetButton() {
96
+ const sim = useMujoco();
97
+ if (!sim.isReady) return null;
98
+
99
+ return <button onClick={() => sim.api.reset()}>Reset</button>;
122
100
  }
123
101
  ```
124
102
 
125
- The `api.applyForce(bodyName, force)` convenience method also works for one-off interactions (e.g. button clicks) but does a name lookup each call.
126
-
127
- ### WebSocket Control
103
+ ## WebSocket Control
128
104
 
129
105
  Stream actuator commands over a WebSocket and send simulation state back. Use a schema validator such as Zod at this boundary because socket messages are untrusted app input (`npm install zod` for this example):
130
106
 
@@ -133,32 +109,28 @@ import { useEffect, useRef } from "react";
133
109
  import { z } from "zod";
134
110
  import { useMujoco, useBeforePhysicsStep, useAfterPhysicsStep } from "mujoco-react";
135
111
 
136
- const CtrlCommand = z.object({
137
- type: z.literal("ctrl_command"),
138
- ctrl: z.array(z.number()),
139
- });
140
-
141
- type CtrlCommand = z.infer<typeof CtrlCommand>;
142
-
143
- function parseSocketMessage(data: string): CtrlCommand | null {
112
+ const CtrlCommand = z.preprocess((data) => {
144
113
  try {
145
- const parsed = CtrlCommand.safeParse(JSON.parse(data));
146
- return parsed.success ? parsed.data : null;
114
+ return typeof data === "string" ? JSON.parse(data) : data;
147
115
  } catch {
148
- return null;
116
+ return undefined;
149
117
  }
150
- }
118
+ }, z.object({
119
+ type: z.literal("ctrl_command"),
120
+ ctrl: z.array(z.number()),
121
+ }));
151
122
 
152
123
  function useWebSocketControls(url: string) {
153
124
  const wsRef = useRef<WebSocket | null>(null);
154
- const latestCommandRef = useRef<CtrlCommand | null>(null);
125
+ const latestCtrlRef = useRef<number[] | null>(null);
155
126
 
156
127
  useEffect(() => {
157
128
  const ws = new WebSocket(url);
158
129
  wsRef.current = ws;
159
130
 
160
131
  ws.onmessage = (evt) => {
161
- latestCommandRef.current = parseSocketMessage(evt.data);
132
+ const command = CtrlCommand.safeParse(evt.data);
133
+ if (command.success) latestCtrlRef.current = command.data.ctrl;
162
134
  };
163
135
 
164
136
  return () => ws.close();
@@ -166,10 +138,10 @@ function useWebSocketControls(url: string) {
166
138
 
167
139
  // Apply incoming actuator controls each physics step.
168
140
  useBeforePhysicsStep((model, data) => {
169
- const command = latestCommandRef.current;
170
- if (!command) return;
171
- for (let i = 0; i < Math.min(command.ctrl.length, model.nu); i++) {
172
- data.ctrl[i] = command.ctrl[i];
141
+ const ctrl = latestCtrlRef.current;
142
+ if (!ctrl) return;
143
+ for (let i = 0; i < Math.min(ctrl.length, model.nu); i++) {
144
+ data.ctrl[i] = ctrl[i];
173
145
  }
174
146
  });
175
147
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "8.4.0",
3
+ "version": "8.4.2",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",