mujoco-react 8.10.0 → 8.11.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 +65 -28
- package/dist/chunk-SEWQULWO.js +400 -0
- package/dist/chunk-SEWQULWO.js.map +1 -0
- package/dist/index.d.ts +82 -14
- package/dist/index.js +289 -17
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +24 -3
- package/dist/spark.js +91 -6
- package/dist/spark.js.map +1 -1
- package/dist/{types-FFW7ykBu.d.ts → types-BmneHLBM.d.ts} +59 -5
- package/package.json +1 -1
- package/src/components/Body.tsx +3 -1
- package/src/components/VisualScenario.tsx +343 -6
- package/src/core/MujocoCanvas.tsx +8 -1
- package/src/core/SceneLoader.ts +182 -3
- package/src/hooks/useFrameCapture.ts +206 -0
- package/src/hooks/usePolicy.ts +12 -8
- package/src/index.ts +25 -0
- package/src/spark.tsx +138 -4
- package/src/types.ts +69 -4
- package/dist/chunk-KGFRKPLS.js +0 -186
- package/dist/chunk-KGFRKPLS.js.map +0 -1
package/README.md
CHANGED
|
@@ -145,20 +145,42 @@ Use it as a child of `<MujocoCanvas>`:
|
|
|
145
145
|
|
|
146
146
|
Gaussian splats are visual context; MuJoCo XML remains the source of physics, contacts, and task fixtures. Pair each splat asset with collision proxy metadata so scene variants, rollouts, and datasets preserve both sides of the environment.
|
|
147
147
|
|
|
148
|
-
Use the
|
|
148
|
+
Use `VisualScenarioEffects` when the same MuJoCo task should render under
|
|
149
|
+
different camera exposure, fog/background, and deterministic material variants:
|
|
149
150
|
|
|
150
151
|
```tsx
|
|
151
|
-
import {
|
|
152
|
-
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
152
|
+
import { ScenarioLighting, VisualScenarioEffects } from "mujoco-react";
|
|
153
|
+
|
|
154
|
+
<MujocoCanvas config={sceneConfig}>
|
|
155
|
+
<VisualScenarioEffects
|
|
156
|
+
scenario={scenario}
|
|
157
|
+
materialFilter={(object) => object.name.startsWith("prop_")}
|
|
158
|
+
/>
|
|
159
|
+
<ScenarioLighting preset={scenario.lighting} />
|
|
160
|
+
</MujocoCanvas>;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Use the renderer-agnostic boundary from the main package. If your app stores
|
|
164
|
+
visual scenarios as data, pass the scenario directly; the component resolves the
|
|
165
|
+
splat asset and paired MJCF collision proxy metadata for you.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { SplatEnvironment, withSplatEnvironment } from "mujoco-react";
|
|
169
|
+
|
|
170
|
+
<SplatEnvironment scenario={scenario} renderer="custom" />;
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
For MuJoCo + 3DGS composition, derive the collision environment from the same
|
|
174
|
+
splat metadata and pass the resulting config to `<MujocoCanvas>`:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
const sceneConfig = withSplatEnvironment(
|
|
178
|
+
{
|
|
179
|
+
src: "/models/xlerobot/",
|
|
180
|
+
sceneFile: "xlerobot.xml",
|
|
181
|
+
},
|
|
182
|
+
kitchenScenario
|
|
183
|
+
);
|
|
162
184
|
```
|
|
163
185
|
|
|
164
186
|
For first-class Spark rendering, install Spark and import the optional adapter:
|
|
@@ -168,23 +190,26 @@ npm install @sparkjsdev/spark
|
|
|
168
190
|
```
|
|
169
191
|
|
|
170
192
|
```tsx
|
|
171
|
-
import {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
import {
|
|
194
|
+
SparkSplatEnvironment,
|
|
195
|
+
useSparkSplatLifecycle,
|
|
196
|
+
} from "mujoco-react/spark";
|
|
197
|
+
|
|
198
|
+
function Scene() {
|
|
199
|
+
const splat = useSparkSplatLifecycle();
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<MujocoCanvas config={sceneConfig} gl={{ preserveDrawingBuffer: true }}>
|
|
203
|
+
<SparkSplatEnvironment scenario={scenario} hideGroundMeshes {...splat.props} />
|
|
204
|
+
<StatusBadge status={splat.status} error={splat.error} />
|
|
205
|
+
</MujocoCanvas>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
186
208
|
```
|
|
187
209
|
|
|
210
|
+
`SparkSplatEnvironment` currently renders `.spz` assets. Use the renderer-agnostic
|
|
211
|
+
`SplatEnvironment` for `.ply`/`.splat` metadata or when wiring a different renderer.
|
|
212
|
+
|
|
188
213
|
## Write Controllers
|
|
189
214
|
|
|
190
215
|
```tsx
|
|
@@ -497,6 +522,7 @@ interface SceneConfig {
|
|
|
497
522
|
src: string; // Base URL for model files
|
|
498
523
|
sceneFile: string; // Entry XML/URDF file, e.g. "scene.xml"
|
|
499
524
|
files?: File[]; // Local files for browser upload workflows
|
|
525
|
+
environmentFiles?: string[]; // Static MJCF environment XMLs merged before compile
|
|
500
526
|
sceneObjects?: SceneObject[]; // Objects injected into scene XML at load time
|
|
501
527
|
homeJoints?: number[]; // Initial joint positions
|
|
502
528
|
xmlPatches?: XmlPatch[]; // Patches applied to XML files during loading
|
|
@@ -504,6 +530,16 @@ interface SceneConfig {
|
|
|
504
530
|
}
|
|
505
531
|
```
|
|
506
532
|
|
|
533
|
+
Use `environmentFiles` to compose reusable physics/collision layers with a robot model. For Gaussian splat scenes, keep the `.spz` as a parallel visual layer and point `environmentFiles` at the paired MJCF proxy scene:
|
|
534
|
+
|
|
535
|
+
```tsx
|
|
536
|
+
const kitchenRobot: SceneConfig = {
|
|
537
|
+
src: "/models/xlerobot/",
|
|
538
|
+
sceneFile: "xlerobot.xml",
|
|
539
|
+
environmentFiles: ["splats/tabletop/scene.xml"],
|
|
540
|
+
};
|
|
541
|
+
```
|
|
542
|
+
|
|
507
543
|
### Local Files and URDF
|
|
508
544
|
|
|
509
545
|
Load browser-selected MJCF or URDF files directly. Folder uploads preserve `webkitRelativePath`, and flat uploads fall back to matching referenced mesh/texture assets by basename:
|
|
@@ -905,7 +941,8 @@ const obs = useObservation({ qpos: true, qvel: true, projectedGravity: "torso" }
|
|
|
905
941
|
const policy = usePolicy({
|
|
906
942
|
frequency: 50,
|
|
907
943
|
onObservation: () => obs.readValues(),
|
|
908
|
-
|
|
944
|
+
infer: ({ observation }) => policySession.run(observation),
|
|
945
|
+
onAction: ({ action, data }) => applyAction(action, data),
|
|
909
946
|
});
|
|
910
947
|
```
|
|
911
948
|
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { useThree } from '@react-three/fiber';
|
|
2
|
+
import { useEffect, useMemo } from 'react';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/components/VisualScenario.tsx
|
|
7
|
+
var DEFAULT_BACKGROUND = "#181a1f";
|
|
8
|
+
function ScenarioLighting({
|
|
9
|
+
preset = "studio",
|
|
10
|
+
castShadow = true,
|
|
11
|
+
intensity = 1
|
|
12
|
+
}) {
|
|
13
|
+
if (preset === "warehouse") {
|
|
14
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15
|
+
/* @__PURE__ */ jsx("ambientLight", { intensity: 0.18 * intensity }),
|
|
16
|
+
/* @__PURE__ */ jsx(
|
|
17
|
+
"directionalLight",
|
|
18
|
+
{
|
|
19
|
+
position: [3.5, -2, 5],
|
|
20
|
+
intensity: 2.2 * intensity,
|
|
21
|
+
castShadow
|
|
22
|
+
}
|
|
23
|
+
),
|
|
24
|
+
/* @__PURE__ */ jsx("directionalLight", { position: [-2, 1.5, 2.5], intensity: 0.25 * intensity })
|
|
25
|
+
] });
|
|
26
|
+
}
|
|
27
|
+
if (preset === "low-light") {
|
|
28
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
29
|
+
/* @__PURE__ */ jsx("ambientLight", { intensity: 0.08 * intensity }),
|
|
30
|
+
/* @__PURE__ */ jsx(
|
|
31
|
+
"directionalLight",
|
|
32
|
+
{
|
|
33
|
+
position: [2, -2, 3],
|
|
34
|
+
intensity: 0.75 * intensity,
|
|
35
|
+
castShadow
|
|
36
|
+
}
|
|
37
|
+
),
|
|
38
|
+
/* @__PURE__ */ jsx("pointLight", { position: [-0.5, -0.8, 1.3], intensity: 0.6 * intensity })
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
if (preset === "splat") {
|
|
42
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
43
|
+
/* @__PURE__ */ jsx("ambientLight", { intensity: 0.42 * intensity }),
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
"directionalLight",
|
|
46
|
+
{
|
|
47
|
+
position: [1.8, -2.4, 3.5],
|
|
48
|
+
intensity: 1.2 * intensity,
|
|
49
|
+
castShadow
|
|
50
|
+
}
|
|
51
|
+
),
|
|
52
|
+
/* @__PURE__ */ jsx("pointLight", { position: [0.4, 0.2, 1.4], intensity: 0.35 * intensity })
|
|
53
|
+
] });
|
|
54
|
+
}
|
|
55
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56
|
+
/* @__PURE__ */ jsx("ambientLight", { intensity: 0.35 * intensity }),
|
|
57
|
+
/* @__PURE__ */ jsx(
|
|
58
|
+
"directionalLight",
|
|
59
|
+
{
|
|
60
|
+
position: [2.5, -3, 4],
|
|
61
|
+
intensity: 1.6 * intensity,
|
|
62
|
+
castShadow
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
] });
|
|
66
|
+
}
|
|
67
|
+
function getScenarioBackground(preset, fallback = DEFAULT_BACKGROUND) {
|
|
68
|
+
if (preset === "warehouse") return "#20242b";
|
|
69
|
+
if (preset === "low-light") return "#0f1115";
|
|
70
|
+
if (preset === "splat") return "#1b1f24";
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
function getScenarioCameraPosition(basePosition, scenario) {
|
|
74
|
+
const [x, y, z] = basePosition;
|
|
75
|
+
const jitter = scenario?.camera?.jitter ?? 0;
|
|
76
|
+
return [
|
|
77
|
+
Number((x + jitter * 0.6).toFixed(3)),
|
|
78
|
+
Number((y - jitter * 0.4).toFixed(3)),
|
|
79
|
+
Number((z + jitter * 0.25).toFixed(3))
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
function VisualScenarioEffects(props) {
|
|
83
|
+
useVisualScenarioEffects(props);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function useVisualScenarioEffects({
|
|
87
|
+
scenario,
|
|
88
|
+
enabled = true,
|
|
89
|
+
applyBackground = true,
|
|
90
|
+
applyFog = true,
|
|
91
|
+
applyRenderer = true,
|
|
92
|
+
applyMaterials = true,
|
|
93
|
+
background,
|
|
94
|
+
fogNear,
|
|
95
|
+
fogFar,
|
|
96
|
+
materialFilter
|
|
97
|
+
}) {
|
|
98
|
+
const { gl, scene, invalidate } = useThree();
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!enabled || !scenario) {
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
const previousExposure = gl.toneMappingExposure;
|
|
104
|
+
const previousBackground = scene.background;
|
|
105
|
+
const previousFog = scene.fog;
|
|
106
|
+
const materialSnapshots = /* @__PURE__ */ new Map();
|
|
107
|
+
if (applyRenderer) {
|
|
108
|
+
gl.toneMappingExposure = scenario.camera?.exposure ?? 1;
|
|
109
|
+
}
|
|
110
|
+
if (applyBackground) {
|
|
111
|
+
scene.background = new THREE.Color(
|
|
112
|
+
background ?? getScenarioBackground(scenario.lighting)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (applyFog) {
|
|
116
|
+
scene.fog = createScenarioFog(scenario, background, fogNear, fogFar);
|
|
117
|
+
}
|
|
118
|
+
if (applyMaterials && scenario.materials) {
|
|
119
|
+
applyScenarioMaterials(scene, scenario, materialSnapshots, materialFilter);
|
|
120
|
+
}
|
|
121
|
+
invalidate();
|
|
122
|
+
return () => {
|
|
123
|
+
gl.toneMappingExposure = previousExposure;
|
|
124
|
+
scene.background = previousBackground;
|
|
125
|
+
scene.fog = previousFog;
|
|
126
|
+
for (const [material, snapshot] of materialSnapshots) {
|
|
127
|
+
const mutable = getMutableScenarioMaterial(material);
|
|
128
|
+
if (!mutable) continue;
|
|
129
|
+
if (snapshot.color) mutable.color.copy(snapshot.color);
|
|
130
|
+
if (typeof snapshot.roughness === "number") {
|
|
131
|
+
mutable.roughness = snapshot.roughness;
|
|
132
|
+
}
|
|
133
|
+
if (typeof snapshot.metalness === "number") {
|
|
134
|
+
mutable.metalness = snapshot.metalness;
|
|
135
|
+
}
|
|
136
|
+
mutable.needsUpdate = true;
|
|
137
|
+
}
|
|
138
|
+
invalidate();
|
|
139
|
+
};
|
|
140
|
+
}, [
|
|
141
|
+
applyBackground,
|
|
142
|
+
applyFog,
|
|
143
|
+
applyMaterials,
|
|
144
|
+
applyRenderer,
|
|
145
|
+
background,
|
|
146
|
+
enabled,
|
|
147
|
+
fogFar,
|
|
148
|
+
fogNear,
|
|
149
|
+
gl,
|
|
150
|
+
invalidate,
|
|
151
|
+
materialFilter,
|
|
152
|
+
scenario,
|
|
153
|
+
scene
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
function SplatEnvironment({
|
|
157
|
+
environment,
|
|
158
|
+
scenario,
|
|
159
|
+
renderer,
|
|
160
|
+
src,
|
|
161
|
+
format,
|
|
162
|
+
collisionProxy,
|
|
163
|
+
collisionProxyMetadata,
|
|
164
|
+
children,
|
|
165
|
+
showPlaceholder = true,
|
|
166
|
+
...groupProps
|
|
167
|
+
}) {
|
|
168
|
+
const metadata = useSplatEnvironment({
|
|
169
|
+
environment,
|
|
170
|
+
scenario,
|
|
171
|
+
renderer,
|
|
172
|
+
src,
|
|
173
|
+
format,
|
|
174
|
+
collisionProxy: collisionProxyMetadata
|
|
175
|
+
});
|
|
176
|
+
const existingUserData = typeof groupProps.userData === "object" && groupProps.userData !== null ? groupProps.userData : {};
|
|
177
|
+
return /* @__PURE__ */ jsxs(
|
|
178
|
+
"group",
|
|
179
|
+
{
|
|
180
|
+
...groupProps,
|
|
181
|
+
userData: {
|
|
182
|
+
...existingUserData,
|
|
183
|
+
...metadata.userData
|
|
184
|
+
},
|
|
185
|
+
children: [
|
|
186
|
+
children,
|
|
187
|
+
children || !showPlaceholder ? null : /* @__PURE__ */ jsx(SplatPlaceholder, {}),
|
|
188
|
+
collisionProxy
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function useSplatEnvironment({
|
|
194
|
+
environment,
|
|
195
|
+
scenario,
|
|
196
|
+
renderer,
|
|
197
|
+
src,
|
|
198
|
+
format,
|
|
199
|
+
collisionProxy
|
|
200
|
+
}) {
|
|
201
|
+
const scenarioEnvironment = useMemo(
|
|
202
|
+
() => environment ?? (scenario ? createPairedSplatEnvironment(scenario, { renderer }) : void 0),
|
|
203
|
+
[environment, renderer, scenario]
|
|
204
|
+
);
|
|
205
|
+
const resolvedSrc = src ?? scenarioEnvironment?.splat.src ?? scenario?.splat?.src;
|
|
206
|
+
const resolvedFormat = format ?? scenarioEnvironment?.splat.format ?? scenario?.splat?.format ?? "spz";
|
|
207
|
+
const resolvedCollisionProxy = collisionProxy ?? scenarioEnvironment?.collisionProxy ?? scenario?.splat?.collisionProxy ?? void 0;
|
|
208
|
+
return useMemo(
|
|
209
|
+
() => ({
|
|
210
|
+
src: resolvedSrc,
|
|
211
|
+
format: resolvedFormat,
|
|
212
|
+
collisionProxy: resolvedCollisionProxy,
|
|
213
|
+
userData: createSplatEnvironmentUserData({
|
|
214
|
+
environment: scenarioEnvironment,
|
|
215
|
+
src: resolvedSrc,
|
|
216
|
+
format: resolvedFormat,
|
|
217
|
+
collisionProxy: resolvedCollisionProxy
|
|
218
|
+
})
|
|
219
|
+
}),
|
|
220
|
+
[scenarioEnvironment, resolvedSrc, resolvedFormat, resolvedCollisionProxy]
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
function createPairedSplatEnvironment(scenario, options = {}) {
|
|
224
|
+
const splat = scenario.splat;
|
|
225
|
+
const collisionProxy = splat?.collisionProxy;
|
|
226
|
+
if (!splat?.enabled || !splat.src || !collisionProxy?.xmlPath) {
|
|
227
|
+
return void 0;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
id: options.id ?? scenario.id ?? "splat-environment",
|
|
231
|
+
label: options.label ?? scenario.label ?? "Gaussian splat environment",
|
|
232
|
+
description: options.description ?? (scenario.environment ? `Visual ${scenario.environment} splat paired with MJCF collision proxy.` : void 0),
|
|
233
|
+
splat: {
|
|
234
|
+
src: splat.src,
|
|
235
|
+
format: splat.format ?? "spz",
|
|
236
|
+
renderer: options.renderer
|
|
237
|
+
},
|
|
238
|
+
collisionProxy: {
|
|
239
|
+
...collisionProxy,
|
|
240
|
+
xmlPath: collisionProxy.xmlPath
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function isPairedSplatEnvironment(input) {
|
|
245
|
+
return !!input && "collisionProxy" in input && "splat" in input;
|
|
246
|
+
}
|
|
247
|
+
function sceneRelativePath(sceneConfig, path) {
|
|
248
|
+
const src = sceneConfig.src;
|
|
249
|
+
if (!src) return path;
|
|
250
|
+
const base = src.endsWith("/") ? src : src + "/";
|
|
251
|
+
if (path.startsWith(base)) return path.slice(base.length);
|
|
252
|
+
return path;
|
|
253
|
+
}
|
|
254
|
+
function uniquePaths(paths) {
|
|
255
|
+
const seen = /* @__PURE__ */ new Set();
|
|
256
|
+
const result = [];
|
|
257
|
+
for (const path of paths) {
|
|
258
|
+
if (seen.has(path)) continue;
|
|
259
|
+
seen.add(path);
|
|
260
|
+
result.push(path);
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
function withSplatEnvironment(sceneConfig, input, options = {}) {
|
|
265
|
+
const environment = isPairedSplatEnvironment(input) ? input : input ? createPairedSplatEnvironment(input, options) : void 0;
|
|
266
|
+
const xmlPath = environment?.collisionProxy.xmlPath;
|
|
267
|
+
if (!xmlPath) return sceneConfig;
|
|
268
|
+
return {
|
|
269
|
+
...sceneConfig,
|
|
270
|
+
environmentFiles: uniquePaths([
|
|
271
|
+
...sceneConfig.environmentFiles ?? [],
|
|
272
|
+
sceneRelativePath(sceneConfig, xmlPath)
|
|
273
|
+
])
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function createSplatEnvironmentUserData({
|
|
277
|
+
environment,
|
|
278
|
+
src,
|
|
279
|
+
format = "spz",
|
|
280
|
+
collisionProxy
|
|
281
|
+
}) {
|
|
282
|
+
return {
|
|
283
|
+
role: "splat-environment",
|
|
284
|
+
environmentId: environment?.id,
|
|
285
|
+
environmentLabel: environment?.label,
|
|
286
|
+
splatSrc: src,
|
|
287
|
+
splatFormat: format,
|
|
288
|
+
splatRenderer: environment?.splat.renderer,
|
|
289
|
+
collisionProxyStatus: collisionProxy?.status ?? "missing",
|
|
290
|
+
collisionProxyXmlPath: collisionProxy?.xmlPath,
|
|
291
|
+
collisionProxyPrimitives: collisionProxy?.primitives ?? []
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function createSparkSplatViewerUrl({
|
|
295
|
+
viewerUrl,
|
|
296
|
+
splatSrc
|
|
297
|
+
}) {
|
|
298
|
+
const url = new URL(viewerUrl, "http://mujoco-react.local");
|
|
299
|
+
url.searchParams.set("splat", splatSrc);
|
|
300
|
+
return viewerUrl.startsWith("http") ? url.toString() : `${url.pathname}${url.search}`;
|
|
301
|
+
}
|
|
302
|
+
function SplatPlaceholder() {
|
|
303
|
+
return /* @__PURE__ */ jsx("group", { children: /* @__PURE__ */ jsxs("mesh", { position: [0, 0, 1.2], children: [
|
|
304
|
+
/* @__PURE__ */ jsx("boxGeometry", { args: [2.4, 2.4, 2.4] }),
|
|
305
|
+
/* @__PURE__ */ jsx(
|
|
306
|
+
"meshBasicMaterial",
|
|
307
|
+
{
|
|
308
|
+
color: "#8b8b8b",
|
|
309
|
+
transparent: true,
|
|
310
|
+
opacity: 0.06,
|
|
311
|
+
wireframe: true,
|
|
312
|
+
side: THREE.DoubleSide
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
] }) });
|
|
316
|
+
}
|
|
317
|
+
function createScenarioFog(scenario, background, fogNear, fogFar) {
|
|
318
|
+
if (scenario.lighting === "low-light") {
|
|
319
|
+
return new THREE.Fog(
|
|
320
|
+
background ?? getScenarioBackground(scenario.lighting),
|
|
321
|
+
fogNear ?? 2.5,
|
|
322
|
+
fogFar ?? 9
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
if (scenario.lighting === "warehouse") {
|
|
326
|
+
return new THREE.Fog(
|
|
327
|
+
background ?? getScenarioBackground(scenario.lighting),
|
|
328
|
+
fogNear ?? 5,
|
|
329
|
+
fogFar ?? 16
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
function applyScenarioMaterials(scene, scenario, snapshots, materialFilter) {
|
|
335
|
+
const materials = scenario.materials;
|
|
336
|
+
if (!materials) return;
|
|
337
|
+
scene.traverse((object) => {
|
|
338
|
+
if (!(object instanceof THREE.Mesh)) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
for (const material of normalizeMaterials(object.material)) {
|
|
342
|
+
const mutable = getMutableScenarioMaterial(material);
|
|
343
|
+
if (!mutable) continue;
|
|
344
|
+
if (materialFilter && !materialFilter(object, material)) continue;
|
|
345
|
+
if (!snapshots.has(material)) {
|
|
346
|
+
snapshots.set(material, {
|
|
347
|
+
color: mutable.color.clone(),
|
|
348
|
+
roughness: mutable.roughness,
|
|
349
|
+
metalness: mutable.metalness
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
applyScenarioMaterial(mutable, object, scenario, materials);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function applyScenarioMaterial(material, object, scenario, materials) {
|
|
357
|
+
const seed = scenario.seed ?? 0;
|
|
358
|
+
const objectKey = `${scenario.id ?? "scenario"}:${object.name}:${material.name}:${seed}`;
|
|
359
|
+
const variation = hashToUnitInterval(objectKey);
|
|
360
|
+
if (materials.randomizeObjectColors) {
|
|
361
|
+
material.color.setHSL(variation, 0.38, 0.42);
|
|
362
|
+
}
|
|
363
|
+
if (materials.randomizeTableMaterial) {
|
|
364
|
+
material.roughness = clamp01(
|
|
365
|
+
materials.roughness ?? 0.35 + variation * 0.45
|
|
366
|
+
);
|
|
367
|
+
material.metalness = clamp01(
|
|
368
|
+
materials.metalness ?? variation * 0.12
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
material.needsUpdate = true;
|
|
372
|
+
}
|
|
373
|
+
function normalizeMaterials(material) {
|
|
374
|
+
return Array.isArray(material) ? material : [material];
|
|
375
|
+
}
|
|
376
|
+
function getMutableScenarioMaterial(material) {
|
|
377
|
+
if (material instanceof THREE.MeshStandardMaterial || material instanceof THREE.MeshPhysicalMaterial) {
|
|
378
|
+
return material;
|
|
379
|
+
}
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
function hashToUnitInterval(value) {
|
|
383
|
+
let hash = 2166136261;
|
|
384
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
385
|
+
hash ^= value.charCodeAt(index);
|
|
386
|
+
hash = Math.imul(hash, 16777619);
|
|
387
|
+
}
|
|
388
|
+
return (hash >>> 0) / 4294967295;
|
|
389
|
+
}
|
|
390
|
+
function clamp01(value) {
|
|
391
|
+
return Math.max(0, Math.min(1, value));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* @license
|
|
395
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
396
|
+
*/
|
|
397
|
+
|
|
398
|
+
export { ScenarioLighting, SplatEnvironment, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment, useVisualScenarioEffects, withSplatEnvironment };
|
|
399
|
+
//# sourceMappingURL=chunk-SEWQULWO.js.map
|
|
400
|
+
//# sourceMappingURL=chunk-SEWQULWO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/VisualScenario.tsx"],"names":[],"mappings":"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","file":"chunk-SEWQULWO.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { useThree } from '@react-three/fiber';\nimport type { ThreeElements } from '@react-three/fiber';\nimport type { ReactNode } from 'react';\nimport { useEffect, useMemo } from 'react';\nimport * as THREE from 'three';\nimport type {\n PairedSplatEnvironmentConfig,\n ScenarioMaterialConfig,\n SceneConfig,\n SplatCollisionProxyConfig,\n SplatEnvironmentMetadata,\n SplatEnvironmentMetadataInput,\n SplatFormat,\n SplatRendererKind,\n SplatSceneInput,\n ScenarioLightingPreset,\n ScenarioLightingProps,\n SplatEnvironmentProps,\n VisualScenarioConfig,\n VisualScenarioEffectsProps,\n} from '../types';\n\nconst DEFAULT_BACKGROUND = '#181a1f';\n\nexport function ScenarioLighting({\n preset = 'studio',\n castShadow = true,\n intensity = 1,\n}: ScenarioLightingProps) {\n if (preset === 'warehouse') {\n return (\n <>\n <ambientLight intensity={0.18 * intensity} />\n <directionalLight\n position={[3.5, -2, 5]}\n intensity={2.2 * intensity}\n castShadow={castShadow}\n />\n <directionalLight position={[-2, 1.5, 2.5]} intensity={0.25 * intensity} />\n </>\n );\n }\n\n if (preset === 'low-light') {\n return (\n <>\n <ambientLight intensity={0.08 * intensity} />\n <directionalLight\n position={[2, -2, 3]}\n intensity={0.75 * intensity}\n castShadow={castShadow}\n />\n <pointLight position={[-0.5, -0.8, 1.3]} intensity={0.6 * intensity} />\n </>\n );\n }\n\n if (preset === 'splat') {\n return (\n <>\n <ambientLight intensity={0.42 * intensity} />\n <directionalLight\n position={[1.8, -2.4, 3.5]}\n intensity={1.2 * intensity}\n castShadow={castShadow}\n />\n <pointLight position={[0.4, 0.2, 1.4]} intensity={0.35 * intensity} />\n </>\n );\n }\n\n return (\n <>\n <ambientLight intensity={0.35 * intensity} />\n <directionalLight\n position={[2.5, -3, 4]}\n intensity={1.6 * intensity}\n castShadow={castShadow}\n />\n </>\n );\n}\n\nexport function getScenarioBackground(\n preset: ScenarioLightingPreset | undefined,\n fallback = DEFAULT_BACKGROUND\n) {\n if (preset === 'warehouse') return '#20242b';\n if (preset === 'low-light') return '#0f1115';\n if (preset === 'splat') return '#1b1f24';\n return fallback;\n}\n\nexport function getScenarioCameraPosition(\n basePosition: readonly [number, number, number],\n scenario?: Pick<VisualScenarioConfig, 'camera'>\n): [number, number, number] {\n const [x, y, z] = basePosition;\n const jitter = scenario?.camera?.jitter ?? 0;\n\n return [\n Number((x + jitter * 0.6).toFixed(3)),\n Number((y - jitter * 0.4).toFixed(3)),\n Number((z + jitter * 0.25).toFixed(3)),\n ];\n}\n\nexport function VisualScenarioEffects(props: VisualScenarioEffectsProps) {\n useVisualScenarioEffects(props);\n return null;\n}\n\nexport function useVisualScenarioEffects({\n scenario,\n enabled = true,\n applyBackground = true,\n applyFog = true,\n applyRenderer = true,\n applyMaterials = true,\n background,\n fogNear,\n fogFar,\n materialFilter,\n}: VisualScenarioEffectsProps) {\n const { gl, scene, invalidate } = useThree();\n\n useEffect(() => {\n if (!enabled || !scenario) {\n return undefined;\n }\n\n const previousExposure = gl.toneMappingExposure;\n const previousBackground = scene.background;\n const previousFog = scene.fog;\n const materialSnapshots = new Map<\n THREE.Material,\n {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n }\n >();\n\n if (applyRenderer) {\n gl.toneMappingExposure = scenario.camera?.exposure ?? 1;\n }\n\n if (applyBackground) {\n scene.background = new THREE.Color(\n background ?? getScenarioBackground(scenario.lighting)\n );\n }\n\n if (applyFog) {\n scene.fog = createScenarioFog(scenario, background, fogNear, fogFar);\n }\n\n if (applyMaterials && scenario.materials) {\n applyScenarioMaterials(scene, scenario, materialSnapshots, materialFilter);\n }\n\n invalidate();\n\n return () => {\n gl.toneMappingExposure = previousExposure;\n scene.background = previousBackground;\n scene.fog = previousFog;\n\n for (const [material, snapshot] of materialSnapshots) {\n const mutable = getMutableScenarioMaterial(material);\n if (!mutable) continue;\n if (snapshot.color) mutable.color.copy(snapshot.color);\n if (typeof snapshot.roughness === 'number') {\n mutable.roughness = snapshot.roughness;\n }\n if (typeof snapshot.metalness === 'number') {\n mutable.metalness = snapshot.metalness;\n }\n mutable.needsUpdate = true;\n }\n\n invalidate();\n };\n }, [\n applyBackground,\n applyFog,\n applyMaterials,\n applyRenderer,\n background,\n enabled,\n fogFar,\n fogNear,\n gl,\n invalidate,\n materialFilter,\n scenario,\n scene,\n ]);\n}\n\n/**\n * Renderer-agnostic Gaussian splat environment boundary.\n *\n * This component intentionally does not import a specific 3DGS renderer. Pass a\n * Spark/GaussianSplats3D object as `children` once the app chooses a renderer,\n * and pass MuJoCo/MJCF collision proxy visuals via `collisionProxy`.\n */\nexport function SplatEnvironment({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy,\n collisionProxyMetadata,\n children,\n showPlaceholder = true,\n ...groupProps\n}: SplatEnvironmentProps) {\n const metadata = useSplatEnvironment({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy: collisionProxyMetadata,\n });\n const existingUserData =\n typeof groupProps.userData === 'object' && groupProps.userData !== null\n ? groupProps.userData\n : {};\n\n return (\n <group\n {...groupProps}\n userData={{\n ...existingUserData,\n ...metadata.userData,\n }}\n >\n {children}\n {children || !showPlaceholder ? null : <SplatPlaceholder />}\n {collisionProxy}\n </group>\n );\n}\n\nexport function useSplatEnvironment({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy,\n}: SplatEnvironmentMetadataInput): SplatEnvironmentMetadata {\n const scenarioEnvironment = useMemo(\n () =>\n environment ??\n (scenario\n ? createPairedSplatEnvironment(scenario, { renderer })\n : undefined),\n [environment, renderer, scenario]\n );\n const resolvedSrc = src ?? scenarioEnvironment?.splat.src ?? scenario?.splat?.src;\n const resolvedFormat =\n format ??\n scenarioEnvironment?.splat.format ??\n scenario?.splat?.format ??\n 'spz';\n const resolvedCollisionProxy =\n collisionProxy ??\n scenarioEnvironment?.collisionProxy ??\n scenario?.splat?.collisionProxy ??\n undefined;\n\n return useMemo(\n () => ({\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n userData: createSplatEnvironmentUserData({\n environment: scenarioEnvironment,\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n }),\n }),\n [scenarioEnvironment, resolvedSrc, resolvedFormat, resolvedCollisionProxy]\n );\n}\n\n/**\n * Convert a generic visual scenario splat block into a paired visual/physics\n * environment config. Returns undefined until both the splat asset and MJCF\n * collision proxy are present.\n */\nexport function createPairedSplatEnvironment(\n scenario: Pick<VisualScenarioConfig, 'id' | 'label' | 'environment' | 'splat'>,\n options: {\n id?: string;\n label?: string;\n description?: string;\n renderer?: SplatRendererKind;\n } = {}\n): PairedSplatEnvironmentConfig | undefined {\n const splat = scenario.splat;\n const collisionProxy = splat?.collisionProxy;\n\n if (!splat?.enabled || !splat.src || !collisionProxy?.xmlPath) {\n return undefined;\n }\n\n return {\n id: options.id ?? scenario.id ?? 'splat-environment',\n label: options.label ?? scenario.label ?? 'Gaussian splat environment',\n description:\n options.description ??\n (scenario.environment\n ? `Visual ${scenario.environment} splat paired with MJCF collision proxy.`\n : undefined),\n splat: {\n src: splat.src,\n format: splat.format ?? 'spz',\n renderer: options.renderer,\n },\n collisionProxy: {\n ...collisionProxy,\n xmlPath: collisionProxy.xmlPath,\n },\n };\n}\n\nfunction isPairedSplatEnvironment(input: SplatSceneInput): input is PairedSplatEnvironmentConfig {\n return !!input && 'collisionProxy' in input && 'splat' in input;\n}\n\nfunction sceneRelativePath(sceneConfig: SceneConfig, path: string): string {\n const src = sceneConfig.src;\n if (!src) return path;\n\n const base = src.endsWith('/') ? src : src + '/';\n if (path.startsWith(base)) return path.slice(base.length);\n return path;\n}\n\nfunction uniquePaths(paths: readonly string[]): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n for (const path of paths) {\n if (seen.has(path)) continue;\n seen.add(path);\n result.push(path);\n }\n return result;\n}\n\n/**\n * Compose a MuJoCo scene config with a paired splat collision proxy.\n *\n * This keeps the common hybrid setup declarative:\n * robot XML remains `sceneFile`, the `.spz` remains a visual-only layer, and\n * the paired MJCF collision proxy is added to `environmentFiles`.\n */\nexport function withSplatEnvironment(\n sceneConfig: SceneConfig,\n input: SplatSceneInput,\n options: { renderer?: SplatRendererKind } = {}\n): SceneConfig {\n const environment = isPairedSplatEnvironment(input)\n ? input\n : input\n ? createPairedSplatEnvironment(input, options)\n : undefined;\n const xmlPath = environment?.collisionProxy.xmlPath;\n if (!xmlPath) return sceneConfig;\n\n return {\n ...sceneConfig,\n environmentFiles: uniquePaths([\n ...(sceneConfig.environmentFiles ?? []),\n sceneRelativePath(sceneConfig, xmlPath),\n ]),\n };\n}\n\nexport function createSplatEnvironmentUserData({\n environment,\n src,\n format = 'spz',\n collisionProxy,\n}: {\n environment?: PairedSplatEnvironmentConfig;\n src?: string;\n format?: SplatFormat;\n collisionProxy?: SplatCollisionProxyConfig;\n}) {\n return {\n role: 'splat-environment',\n environmentId: environment?.id,\n environmentLabel: environment?.label,\n splatSrc: src,\n splatFormat: format,\n splatRenderer: environment?.splat.renderer,\n collisionProxyStatus: collisionProxy?.status ?? 'missing',\n collisionProxyXmlPath: collisionProxy?.xmlPath,\n collisionProxyPrimitives: collisionProxy?.primitives ?? [],\n };\n}\n\nexport function createSparkSplatViewerUrl({\n viewerUrl,\n splatSrc,\n}: {\n viewerUrl: string;\n splatSrc: string;\n}) {\n const url = new URL(viewerUrl, 'http://mujoco-react.local');\n url.searchParams.set('splat', splatSrc);\n return viewerUrl.startsWith('http') ? url.toString() : `${url.pathname}${url.search}`;\n}\n\nfunction SplatPlaceholder() {\n return (\n <group>\n <mesh position={[0, 0, 1.2]}>\n <boxGeometry args={[2.4, 2.4, 2.4]} />\n <meshBasicMaterial\n color=\"#8b8b8b\"\n transparent\n opacity={0.06}\n wireframe\n side={THREE.DoubleSide}\n />\n </mesh>\n </group>\n );\n}\n\nfunction createScenarioFog(\n scenario: VisualScenarioConfig,\n background: THREE.ColorRepresentation | undefined,\n fogNear: number | undefined,\n fogFar: number | undefined\n) {\n if (scenario.lighting === 'low-light') {\n return new THREE.Fog(\n background ?? getScenarioBackground(scenario.lighting),\n fogNear ?? 2.5,\n fogFar ?? 9\n );\n }\n\n if (scenario.lighting === 'warehouse') {\n return new THREE.Fog(\n background ?? getScenarioBackground(scenario.lighting),\n fogNear ?? 5,\n fogFar ?? 16\n );\n }\n\n return null;\n}\n\nfunction applyScenarioMaterials(\n scene: THREE.Scene,\n scenario: VisualScenarioConfig,\n snapshots: Map<\n THREE.Material,\n {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n }\n >,\n materialFilter: VisualScenarioEffectsProps['materialFilter']\n) {\n const materials = scenario.materials;\n if (!materials) return;\n\n scene.traverse((object) => {\n if (!(object instanceof THREE.Mesh)) {\n return;\n }\n\n for (const material of normalizeMaterials(object.material)) {\n const mutable = getMutableScenarioMaterial(material);\n if (!mutable) continue;\n if (materialFilter && !materialFilter(object, material)) continue;\n\n if (!snapshots.has(material)) {\n snapshots.set(material, {\n color: mutable.color.clone(),\n roughness: mutable.roughness,\n metalness: mutable.metalness,\n });\n }\n\n applyScenarioMaterial(mutable, object, scenario, materials);\n }\n });\n}\n\nfunction applyScenarioMaterial(\n material: THREE.MeshStandardMaterial | THREE.MeshPhysicalMaterial,\n object: THREE.Object3D,\n scenario: VisualScenarioConfig,\n materials: ScenarioMaterialConfig\n) {\n const seed = scenario.seed ?? 0;\n const objectKey = `${scenario.id ?? 'scenario'}:${object.name}:${material.name}:${seed}`;\n const variation = hashToUnitInterval(objectKey);\n\n if (materials.randomizeObjectColors) {\n material.color.setHSL(variation, 0.38, 0.42);\n }\n\n if (materials.randomizeTableMaterial) {\n material.roughness = clamp01(\n materials.roughness ?? 0.35 + variation * 0.45\n );\n material.metalness = clamp01(\n materials.metalness ?? variation * 0.12\n );\n }\n\n material.needsUpdate = true;\n}\n\nfunction normalizeMaterials(\n material: THREE.Material | THREE.Material[]\n): THREE.Material[] {\n return Array.isArray(material) ? material : [material];\n}\n\nfunction getMutableScenarioMaterial(\n material: THREE.Material\n): THREE.MeshStandardMaterial | THREE.MeshPhysicalMaterial | null {\n if (\n material instanceof THREE.MeshStandardMaterial ||\n material instanceof THREE.MeshPhysicalMaterial\n ) {\n return material;\n }\n\n return null;\n}\n\nfunction hashToUnitInterval(value: string) {\n let hash = 2166136261;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 16777619);\n }\n return (hash >>> 0) / 4294967295;\n}\n\nfunction clamp01(value: number) {\n return Math.max(0, Math.min(1, value));\n}\n\nexport type SplatCollisionProxy = ReactNode | ThreeElements['group'];\n"]}
|