mujoco-react 8.9.2 → 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 +82 -1
- package/dist/chunk-SEWQULWO.js +400 -0
- package/dist/chunk-SEWQULWO.js.map +1 -0
- package/dist/index.d.ts +114 -744
- package/dist/index.js +329 -35
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +53 -0
- package/dist/spark.js +235 -0
- package/dist/spark.js.map +1 -0
- package/dist/types-BmneHLBM.d.ts +871 -0
- package/dist/vite.d.ts +9 -0
- package/dist/vite.js +4 -0
- package/dist/vite.js.map +1 -1
- package/package.json +15 -2
- package/src/components/Body.tsx +3 -1
- package/src/components/VisualScenario.tsx +566 -0
- 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/hooks/useSceneLights.ts +49 -18
- package/src/index.ts +48 -0
- package/src/spark.tsx +336 -0
- package/src/types.ts +159 -3
- package/src/vite.ts +8 -0
package/README.md
CHANGED
|
@@ -141,6 +141,75 @@ Use it as a child of `<MujocoCanvas>`:
|
|
|
141
141
|
</MujocoCanvas>
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
## Gaussian Splat Environments
|
|
145
|
+
|
|
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
|
+
|
|
148
|
+
Use `VisualScenarioEffects` when the same MuJoCo task should render under
|
|
149
|
+
different camera exposure, fog/background, and deterministic material variants:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
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
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
For first-class Spark rendering, install Spark and import the optional adapter:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npm install @sparkjsdev/spark
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
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
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`SparkSplatEnvironment` currently renders `.spz` assets. Use the renderer-agnostic
|
|
211
|
+
`SplatEnvironment` for `.ply`/`.splat` metadata or when wiring a different renderer.
|
|
212
|
+
|
|
144
213
|
## Write Controllers
|
|
145
214
|
|
|
146
215
|
```tsx
|
|
@@ -453,6 +522,7 @@ interface SceneConfig {
|
|
|
453
522
|
src: string; // Base URL for model files
|
|
454
523
|
sceneFile: string; // Entry XML/URDF file, e.g. "scene.xml"
|
|
455
524
|
files?: File[]; // Local files for browser upload workflows
|
|
525
|
+
environmentFiles?: string[]; // Static MJCF environment XMLs merged before compile
|
|
456
526
|
sceneObjects?: SceneObject[]; // Objects injected into scene XML at load time
|
|
457
527
|
homeJoints?: number[]; // Initial joint positions
|
|
458
528
|
xmlPatches?: XmlPatch[]; // Patches applied to XML files during loading
|
|
@@ -460,6 +530,16 @@ interface SceneConfig {
|
|
|
460
530
|
}
|
|
461
531
|
```
|
|
462
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
|
+
|
|
463
543
|
### Local Files and URDF
|
|
464
544
|
|
|
465
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:
|
|
@@ -861,7 +941,8 @@ const obs = useObservation({ qpos: true, qvel: true, projectedGravity: "torso" }
|
|
|
861
941
|
const policy = usePolicy({
|
|
862
942
|
frequency: 50,
|
|
863
943
|
onObservation: () => obs.readValues(),
|
|
864
|
-
|
|
944
|
+
infer: ({ observation }) => policySession.run(observation),
|
|
945
|
+
onAction: ({ action, data }) => applyAction(action, data),
|
|
865
946
|
});
|
|
866
947
|
```
|
|
867
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"]}
|