mujoco-react 9.2.0 → 9.4.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 +225 -15
- package/dist/{chunk-33CV6HSV.js → chunk-VDSEPZYQ.js} +303 -14
- package/dist/chunk-VDSEPZYQ.js.map +1 -0
- package/dist/index.d.ts +274 -7
- package/dist/index.js +1172 -131
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +24 -2
- package/dist/spark.js +89 -3
- package/dist/spark.js.map +1 -1
- package/dist/{types-S8ggQY2n.d.ts → types-BuJ4boaq.d.ts} +160 -5
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +14 -7
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SplatCollisionProxyPreview.tsx +350 -0
- package/src/components/VisualScenario.tsx +287 -11
- package/src/core/MujocoSimProvider.tsx +374 -30
- package/src/core/SceneLoader.ts +13 -0
- package/src/hooks/useMountedCameraSequenceRecorder.ts +155 -0
- package/src/index.ts +80 -0
- package/src/rendering/cameraFrameCapture.ts +195 -26
- package/src/rendering/cameraFrameSource.ts +747 -0
- package/src/spark.tsx +144 -0
- package/src/types.ts +166 -4
- package/src/vite.ts +14 -6
- package/dist/chunk-33CV6HSV.js.map +0 -1
package/README.md
CHANGED
|
@@ -143,7 +143,7 @@ Use it as a child of `<MujocoCanvas>`:
|
|
|
143
143
|
|
|
144
144
|
## Gaussian Splat Environments
|
|
145
145
|
|
|
146
|
-
Gaussian splats are visual context; MuJoCo XML remains the source of physics, contacts, and task fixtures.
|
|
146
|
+
Gaussian splats are visual context; MuJoCo XML remains the source of physics, contacts, and task fixtures. Use visual-only splats when you only need rendered environment context, and add collision proxy metadata when a workflow needs simplified contact geometry.
|
|
147
147
|
|
|
148
148
|
Use `VisualScenarioEffects` when the same MuJoCo task should render under
|
|
149
149
|
different camera exposure, fog/background, and deterministic material variants:
|
|
@@ -162,16 +162,102 @@ import { ScenarioLighting, VisualScenarioEffects } from "mujoco-react";
|
|
|
162
162
|
|
|
163
163
|
Use the renderer-agnostic boundary from the main package. If your app stores
|
|
164
164
|
visual scenarios as data, pass the scenario directly; the component resolves the
|
|
165
|
-
splat asset and paired MJCF collision proxy metadata for you.
|
|
165
|
+
splat asset and any paired MJCF collision proxy metadata for you. Visual-only
|
|
166
|
+
splats are valid, and readiness tells you whether a collision proxy is required
|
|
167
|
+
for your training/physics handoff.
|
|
166
168
|
|
|
167
169
|
```tsx
|
|
168
|
-
import { SplatEnvironment,
|
|
170
|
+
import { MujocoCanvas, SplatEnvironment, useSplatSceneConfig } from "mujoco-react";
|
|
169
171
|
|
|
170
|
-
|
|
172
|
+
const splat = useSplatSceneConfig({ sceneConfig, scenario });
|
|
173
|
+
|
|
174
|
+
<MujocoCanvas config={splat.sceneConfig}>
|
|
175
|
+
{splat.environment ? (
|
|
176
|
+
<SplatEnvironment environment={splat.environment} renderer="custom">
|
|
177
|
+
<MySplatRenderer src={splat.environment.splat.src} />
|
|
178
|
+
</SplatEnvironment>
|
|
179
|
+
) : null}
|
|
180
|
+
</MujocoCanvas>;
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
When a splat scenario includes paired MJCF collision proxy metadata, render a
|
|
184
|
+
generic wireframe preview from that XML with `SplatCollisionProxyPreview`. The
|
|
185
|
+
component parses MJCF primitives such as planes, boxes, spheres, capsules, and
|
|
186
|
+
mesh placeholders from any fetchable proxy XML; the tabletop example is just one
|
|
187
|
+
possible environment.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import {
|
|
191
|
+
SplatCollisionProxyPreview,
|
|
192
|
+
SplatEnvironment,
|
|
193
|
+
useSplatCollisionProxyGeoms,
|
|
194
|
+
useSplatSceneConfig,
|
|
195
|
+
} from "mujoco-react";
|
|
196
|
+
|
|
197
|
+
const splat = useSplatSceneConfig({ sceneConfig, scenario });
|
|
198
|
+
const proxy = splat.environment?.collisionProxy;
|
|
199
|
+
|
|
200
|
+
<SplatEnvironment
|
|
201
|
+
environment={splat.environment}
|
|
202
|
+
collisionProxy={
|
|
203
|
+
proxy ? <SplatCollisionProxyPreview collisionProxy={proxy} /> : undefined
|
|
204
|
+
}
|
|
205
|
+
/>;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Use `useSplatCollisionProxyGeoms()` when your app wants to inspect or style the
|
|
209
|
+
proxy primitives itself:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
const proxyPreview = useSplatCollisionProxyGeoms({
|
|
213
|
+
collisionProxy: splat.environment?.collisionProxy,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
proxyPreview.geoms.map((geom) => geom.type);
|
|
171
217
|
```
|
|
172
218
|
|
|
173
|
-
|
|
174
|
-
|
|
219
|
+
Use `splat.readiness` or `getSplatEnvironmentReadiness(scenario)` to gate
|
|
220
|
+
authoring and import flows. The status distinguishes disabled scenarios,
|
|
221
|
+
missing visual assets, missing collision proxies, unsupported renderer formats,
|
|
222
|
+
and ready environments.
|
|
223
|
+
|
|
224
|
+
Use `createSplatSceneConfig()` when the same scene composition needs to run
|
|
225
|
+
outside React, such as codegen, import validation, backend handoff metadata, or
|
|
226
|
+
tests:
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
import { createSplatSceneConfig } from "mujoco-react";
|
|
230
|
+
|
|
231
|
+
const splat = createSplatSceneConfig({
|
|
232
|
+
sceneConfig,
|
|
233
|
+
scenario,
|
|
234
|
+
renderer: "spark",
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Use `createVisualScenarioExecutionContext()` or
|
|
239
|
+
`useVisualScenarioExecutionContext()` when recording rollouts or exporting
|
|
240
|
+
LeRobot/HF Jobs handoff artifacts. It resolves the scenario seed, camera
|
|
241
|
+
exposure/noise/blur/jitter, material randomization, splat source, collision
|
|
242
|
+
proxy, and readiness into one serializable object.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { createVisualScenarioExecutionContext } from "mujoco-react";
|
|
246
|
+
|
|
247
|
+
const visualContext = createVisualScenarioExecutionContext({
|
|
248
|
+
scenario,
|
|
249
|
+
renderer: "spark",
|
|
250
|
+
variantId,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
writeEpisodeManifest({
|
|
254
|
+
task,
|
|
255
|
+
visualExecutionContext: visualContext,
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
For MuJoCo + 3DGS composition, derive the optional collision environment from
|
|
260
|
+
the same splat metadata and pass the resulting config to `<MujocoCanvas>`:
|
|
175
261
|
|
|
176
262
|
```tsx
|
|
177
263
|
const sceneConfig = withSplatEnvironment(
|
|
@@ -192,16 +278,18 @@ npm install @sparkjsdev/spark
|
|
|
192
278
|
```tsx
|
|
193
279
|
import {
|
|
194
280
|
SparkSplatEnvironment,
|
|
195
|
-
|
|
281
|
+
useSparkSplatEnvironment,
|
|
196
282
|
} from "mujoco-react/spark";
|
|
197
283
|
|
|
198
284
|
function Scene() {
|
|
199
|
-
const splat =
|
|
285
|
+
const splat = useSparkSplatEnvironment({ sceneConfig, scenario });
|
|
200
286
|
|
|
201
287
|
return (
|
|
202
|
-
<MujocoCanvas config={sceneConfig} gl={{ preserveDrawingBuffer: true }}>
|
|
203
|
-
|
|
204
|
-
|
|
288
|
+
<MujocoCanvas config={splat.sceneConfig} gl={{ preserveDrawingBuffer: true }}>
|
|
289
|
+
{splat.environment ? (
|
|
290
|
+
<SparkSplatEnvironment hideGroundMeshes {...splat.props} />
|
|
291
|
+
) : null}
|
|
292
|
+
<StatusBadge status={splat.lifecycle.status} error={splat.lifecycle.error} />
|
|
205
293
|
</MujocoCanvas>
|
|
206
294
|
);
|
|
207
295
|
}
|
|
@@ -537,7 +625,7 @@ interface SceneConfig {
|
|
|
537
625
|
}
|
|
538
626
|
```
|
|
539
627
|
|
|
540
|
-
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
|
|
628
|
+
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 a paired MJCF proxy scene only when contact geometry is needed:
|
|
541
629
|
|
|
542
630
|
```tsx
|
|
543
631
|
const kitchenRobot: SceneConfig = {
|
|
@@ -1009,11 +1097,133 @@ Use `useFrameCapture()` or the standalone `captureFrame()` helpers when you own
|
|
|
1009
1097
|
the canvas or want to capture a custom container.
|
|
1010
1098
|
|
|
1011
1099
|
Use `captureCameraFrame()` / `captureCameraFrameBlob()` when dataset generation
|
|
1012
|
-
needs
|
|
1013
|
-
interactive viewport.
|
|
1100
|
+
needs an offscreen camera render at a stable resolution without moving the
|
|
1101
|
+
user's interactive viewport. Pass `cameraName`, `siteName`, or `bodyName` to
|
|
1102
|
+
record true MuJoCo-mounted camera frames; the returned image includes
|
|
1103
|
+
`source.kind` so dataset pipelines can reject fallback or synthetic fixed poses.
|
|
1014
1104
|
|
|
1015
1105
|
Use `recordCameraSequence()` / `useCameraSequenceRecorder()` to step policy
|
|
1016
|
-
rollouts and capture synchronized frames from one or more
|
|
1106
|
+
rollouts and capture synchronized per-camera frames from one or more MuJoCo
|
|
1107
|
+
camera configs. Sequence recording requires mounted MuJoCo camera, site, or
|
|
1108
|
+
body selectors by default; use still capture APIs for synthetic debug poses.
|
|
1109
|
+
|
|
1110
|
+
For LeRobot-style datasets, prefer the named-camera wrapper. It resolves task
|
|
1111
|
+
camera keys to MuJoCo cameras/sites/bodies, records the sequence, and returns
|
|
1112
|
+
the plan and readiness summary alongside frame provenance:
|
|
1113
|
+
|
|
1114
|
+
```tsx
|
|
1115
|
+
const sequence = await recordMountedCameraFrameSequence(api, {
|
|
1116
|
+
cameraKeys: ["head", "left_wrist", "right_wrist"],
|
|
1117
|
+
aliases: {
|
|
1118
|
+
head: [{ siteName: "head_camera_rgb_optical_frame" }],
|
|
1119
|
+
left_wrist: [{ siteName: "left_wrist_camera_optical_frame" }],
|
|
1120
|
+
right_wrist: [{ siteName: "right_wrist_camera_optical_frame" }],
|
|
1121
|
+
},
|
|
1122
|
+
defaults: {
|
|
1123
|
+
width: 640,
|
|
1124
|
+
height: 480,
|
|
1125
|
+
type: "image/png",
|
|
1126
|
+
fov: 45,
|
|
1127
|
+
near: 0.01,
|
|
1128
|
+
far: 100,
|
|
1129
|
+
},
|
|
1130
|
+
frames: 16,
|
|
1131
|
+
stepsPerFrame: 1,
|
|
1132
|
+
retainFrames: false,
|
|
1133
|
+
requireMountedSources: true,
|
|
1134
|
+
onFrame: ({ frameIndex, cameras }) => {
|
|
1135
|
+
queueLeRobotImages(frameIndex, cameras);
|
|
1136
|
+
},
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
sequence.readiness.ready; // true when every requested stream resolved
|
|
1140
|
+
sequence.plan.missingKeys; // unresolved task cameras, if requireAll is false
|
|
1141
|
+
sequence.cameraSummaries.head.source; // mounted source provenance
|
|
1142
|
+
|
|
1143
|
+
const manifest = createMountedCameraFrameSequenceManifest(sequence);
|
|
1144
|
+
manifest.streamSummaries.head.complete; // per-camera frame coverage
|
|
1145
|
+
manifest.status; // "complete", "partial", or "missing"
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
`recordMountedCameraFrameSequence()` requires all requested `cameraKeys` by
|
|
1149
|
+
default so dataset recording cannot silently omit a camera stream. Set
|
|
1150
|
+
`requireAll: false` only for exploratory tooling that can tolerate partial
|
|
1151
|
+
camera coverage.
|
|
1152
|
+
|
|
1153
|
+
Use `createMountedCameraFrameSequenceManifest()` after recording to persist a
|
|
1154
|
+
stable dataset-facing manifest with readiness, source targets, dimensions,
|
|
1155
|
+
first/last frame indices, timestamps, and per-camera missing-frame counts.
|
|
1156
|
+
|
|
1157
|
+
Inside `<MujocoCanvas>` children, `useMountedCameraSequenceRecorder()` exposes
|
|
1158
|
+
the same planning and recording surface with React status/error/result state.
|
|
1159
|
+
Use `checkReadiness()` before recording when the UI needs a preflight gate for
|
|
1160
|
+
LeRobot camera streams:
|
|
1161
|
+
|
|
1162
|
+
```tsx
|
|
1163
|
+
function DatasetRecorder() {
|
|
1164
|
+
const recorder = useMountedCameraSequenceRecorder({
|
|
1165
|
+
defaults: { width: 640, height: 480, type: "image/png" },
|
|
1166
|
+
aliases: {
|
|
1167
|
+
head: { cameraName: "head" },
|
|
1168
|
+
left_wrist: { siteName: "left_wrist_camera_optical_frame" },
|
|
1169
|
+
right_wrist: { siteName: "right_wrist_camera_optical_frame" },
|
|
1170
|
+
},
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
async function recordDatasetEpisode() {
|
|
1174
|
+
const cameraKeys = ["head", "left_wrist", "right_wrist"];
|
|
1175
|
+
const readiness = recorder.checkReadiness(cameraKeys);
|
|
1176
|
+
if (!readiness.ready) return;
|
|
1177
|
+
|
|
1178
|
+
await recorder.record({
|
|
1179
|
+
cameraKeys,
|
|
1180
|
+
frames: 16,
|
|
1181
|
+
retainFrames: false,
|
|
1182
|
+
onFrame: ({ frameIndex, cameras }) => {
|
|
1183
|
+
queueLeRobotImages(frameIndex, cameras);
|
|
1184
|
+
},
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
return (
|
|
1189
|
+
<button disabled={recorder.isRecording} onClick={recordDatasetEpisode}>
|
|
1190
|
+
Record camera streams
|
|
1191
|
+
</button>
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
`recorder.readiness` keeps the latest preflight result, and
|
|
1197
|
+
`recorder.result?.readiness` keeps the readiness that shipped with the most
|
|
1198
|
+
recent recording.
|
|
1199
|
+
|
|
1200
|
+
Use `resolveMountedCameraFrameSource()` when dataset feature names need to map
|
|
1201
|
+
to named MuJoCo cameras, sites, or bodies before recording. The helper first
|
|
1202
|
+
checks exact names and aliases, then falls back to normalized/prefix/suffix
|
|
1203
|
+
matches such as `left_wrist` -> `left_wrist_camera_optical_frame`, and
|
|
1204
|
+
token-contained imported-model names such as `observation.images.head` ->
|
|
1205
|
+
`robot_head_camera`. It returns both the capture selector and the
|
|
1206
|
+
mounted-source provenance that should be stored beside the dataset:
|
|
1207
|
+
|
|
1208
|
+
```tsx
|
|
1209
|
+
const resolved = resolveMountedCameraFrameSource("head", {
|
|
1210
|
+
cameras: api.getCameras(),
|
|
1211
|
+
sites: api.getSites(),
|
|
1212
|
+
bodies: api.getBodies(),
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
if (!resolved) throw new Error("head does not resolve to a MuJoCo source");
|
|
1216
|
+
|
|
1217
|
+
await api.recordCameraSequence({
|
|
1218
|
+
frames: 16,
|
|
1219
|
+
requireMountedSources: true,
|
|
1220
|
+
cameras: [{ key: "head", width: 640, height: 480, ...resolved.selector }],
|
|
1221
|
+
});
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
Pass `aliases` when multiple MuJoCo resources could match a dataset stream key
|
|
1225
|
+
or when the model uses names that do not share a normalized prefix/suffix with
|
|
1226
|
+
the LeRobot camera feature.
|
|
1017
1227
|
|
|
1018
1228
|
### `useCtrlNoise(config)`
|
|
1019
1229
|
|
|
@@ -1,9 +1,90 @@
|
|
|
1
1
|
import { useThree } from '@react-three/fiber';
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo, useEffect } from 'react';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
|
-
// src/
|
|
6
|
+
// src/types.ts
|
|
7
|
+
var runtimeRobotResources = {};
|
|
8
|
+
var REGISTER_RESOURCE_KEYS = ["actuators", "sensors", "bodies", "joints", "sites", "geoms", "keyframes", "cameras"];
|
|
9
|
+
function createEmptyRuntimeResources() {
|
|
10
|
+
return {
|
|
11
|
+
actuators: {},
|
|
12
|
+
sensors: {},
|
|
13
|
+
bodies: {},
|
|
14
|
+
joints: {},
|
|
15
|
+
sites: {},
|
|
16
|
+
geoms: {},
|
|
17
|
+
keyframes: {},
|
|
18
|
+
cameras: {}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function registerRobotResources(resources) {
|
|
22
|
+
for (const [robot, robotResources] of Object.entries(resources)) {
|
|
23
|
+
const existing = runtimeRobotResources[robot] ?? createEmptyRuntimeResources();
|
|
24
|
+
for (const key of REGISTER_RESOURCE_KEYS) {
|
|
25
|
+
existing[key] = { ...existing[key], ...robotResources[key] ?? {} };
|
|
26
|
+
}
|
|
27
|
+
runtimeRobotResources[robot] = existing;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function createResourceCategory(key) {
|
|
31
|
+
return new Proxy({}, {
|
|
32
|
+
get(_target, robot) {
|
|
33
|
+
if (typeof robot !== "string") return void 0;
|
|
34
|
+
return runtimeRobotResources[robot]?.[key] ?? {};
|
|
35
|
+
},
|
|
36
|
+
ownKeys() {
|
|
37
|
+
return Reflect.ownKeys(runtimeRobotResources);
|
|
38
|
+
},
|
|
39
|
+
getOwnPropertyDescriptor(_target, robot) {
|
|
40
|
+
if (typeof robot !== "string" || !(robot in runtimeRobotResources)) return void 0;
|
|
41
|
+
return { enumerable: true, configurable: true };
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
var RobotResources = new Proxy(runtimeRobotResources, {
|
|
46
|
+
get(target, robot) {
|
|
47
|
+
if (typeof robot !== "string") return void 0;
|
|
48
|
+
return target[robot] ?? createEmptyRuntimeResources();
|
|
49
|
+
},
|
|
50
|
+
ownKeys(target) {
|
|
51
|
+
return Reflect.ownKeys(target);
|
|
52
|
+
},
|
|
53
|
+
getOwnPropertyDescriptor(target, robot) {
|
|
54
|
+
if (typeof robot !== "string" || !(robot in target)) return void 0;
|
|
55
|
+
return { enumerable: true, configurable: true };
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
var RobotActuators = createResourceCategory("actuators");
|
|
59
|
+
var RobotSensors = createResourceCategory("sensors");
|
|
60
|
+
var RobotBodies = createResourceCategory("bodies");
|
|
61
|
+
var RobotJoints = createResourceCategory("joints");
|
|
62
|
+
var RobotSites = createResourceCategory("sites");
|
|
63
|
+
var RobotGeoms = createResourceCategory("geoms");
|
|
64
|
+
var RobotKeyframes = createResourceCategory("keyframes");
|
|
65
|
+
var RobotCameras = createResourceCategory("cameras");
|
|
66
|
+
function getContact(contacts, i) {
|
|
67
|
+
try {
|
|
68
|
+
return contacts.get(i);
|
|
69
|
+
} catch {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function withContacts(data, read) {
|
|
74
|
+
const contacts = data.contact;
|
|
75
|
+
try {
|
|
76
|
+
return read(contacts);
|
|
77
|
+
} finally {
|
|
78
|
+
contacts.delete?.();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
var SplatEnvironmentReadinessStatus = {
|
|
82
|
+
Disabled: "disabled",
|
|
83
|
+
MissingSplat: "missing-splat",
|
|
84
|
+
MissingCollisionProxy: "missing-collision-proxy",
|
|
85
|
+
UnsupportedFormat: "unsupported-format",
|
|
86
|
+
Ready: "ready"
|
|
87
|
+
};
|
|
7
88
|
var DEFAULT_BACKGROUND = "#181a1f";
|
|
8
89
|
function ScenarioLighting({
|
|
9
90
|
preset = "studio",
|
|
@@ -79,6 +160,75 @@ function getScenarioCameraPosition(basePosition, scenario) {
|
|
|
79
160
|
Number((z + jitter * 0.25).toFixed(3))
|
|
80
161
|
];
|
|
81
162
|
}
|
|
163
|
+
function useVisualScenarioExecutionContext({
|
|
164
|
+
scenario,
|
|
165
|
+
environment,
|
|
166
|
+
renderer,
|
|
167
|
+
variantId,
|
|
168
|
+
enabled
|
|
169
|
+
}) {
|
|
170
|
+
return useMemo(
|
|
171
|
+
() => createVisualScenarioExecutionContext({
|
|
172
|
+
scenario,
|
|
173
|
+
environment,
|
|
174
|
+
renderer,
|
|
175
|
+
variantId,
|
|
176
|
+
enabled
|
|
177
|
+
}),
|
|
178
|
+
[enabled, environment, renderer, scenario, variantId]
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
function createVisualScenarioExecutionContext({
|
|
182
|
+
scenario,
|
|
183
|
+
environment,
|
|
184
|
+
renderer,
|
|
185
|
+
variantId,
|
|
186
|
+
enabled = true
|
|
187
|
+
}) {
|
|
188
|
+
const pairedEnvironment = environment ?? (scenario ? createPairedSplatEnvironment(scenario, { renderer }) : void 0);
|
|
189
|
+
const splat = scenario?.splat;
|
|
190
|
+
const collisionProxy = pairedEnvironment?.collisionProxy ?? splat?.collisionProxy ?? void 0;
|
|
191
|
+
const readiness = getSplatEnvironmentReadiness({
|
|
192
|
+
environment: pairedEnvironment,
|
|
193
|
+
scenario,
|
|
194
|
+
renderer,
|
|
195
|
+
enabled
|
|
196
|
+
});
|
|
197
|
+
const format = pairedEnvironment?.splat.format ?? splat?.format ?? readiness.format ?? "spz";
|
|
198
|
+
return {
|
|
199
|
+
scenarioId: scenario?.id ?? pairedEnvironment?.id ?? "visual-scenario",
|
|
200
|
+
scenarioLabel: scenario?.label ?? pairedEnvironment?.label ?? "Visual scenario",
|
|
201
|
+
variantId,
|
|
202
|
+
seed: scenario?.seed ?? 0,
|
|
203
|
+
lighting: scenario?.lighting ?? "studio",
|
|
204
|
+
environment: scenario?.environment,
|
|
205
|
+
camera: {
|
|
206
|
+
jitter: scenario?.camera?.jitter ?? 0,
|
|
207
|
+
exposure: scenario?.camera?.exposure ?? 1,
|
|
208
|
+
noise: scenario?.camera?.noise ?? 0,
|
|
209
|
+
blur: scenario?.camera?.blur ?? 0
|
|
210
|
+
},
|
|
211
|
+
materials: {
|
|
212
|
+
randomizeObjectColors: Boolean(
|
|
213
|
+
scenario?.materials?.randomizeObjectColors
|
|
214
|
+
),
|
|
215
|
+
randomizeTableMaterial: Boolean(
|
|
216
|
+
scenario?.materials?.randomizeTableMaterial
|
|
217
|
+
),
|
|
218
|
+
roughness: scenario?.materials?.roughness,
|
|
219
|
+
metalness: scenario?.materials?.metalness
|
|
220
|
+
},
|
|
221
|
+
splatEnabled: Boolean(splat?.enabled || pairedEnvironment),
|
|
222
|
+
splatSrc: pairedEnvironment?.splat.src ?? splat?.src,
|
|
223
|
+
splatFormat: format,
|
|
224
|
+
splatRenderer: renderer ?? pairedEnvironment?.splat.renderer,
|
|
225
|
+
collisionProxyXmlPath: collisionProxy?.xmlPath,
|
|
226
|
+
collisionProxyStatus: collisionProxy?.status,
|
|
227
|
+
collisionProxyPrimitives: collisionProxy?.primitives ?? [],
|
|
228
|
+
readiness,
|
|
229
|
+
transformSource: "visualScenario.camera"
|
|
230
|
+
};
|
|
231
|
+
}
|
|
82
232
|
function VisualScenarioEffects(props) {
|
|
83
233
|
useVisualScenarioEffects(props);
|
|
84
234
|
return null;
|
|
@@ -205,25 +355,161 @@ function useSplatEnvironment({
|
|
|
205
355
|
const resolvedSrc = src ?? scenarioEnvironment?.splat.src ?? scenario?.splat?.src;
|
|
206
356
|
const resolvedFormat = format ?? scenarioEnvironment?.splat.format ?? scenario?.splat?.format ?? "spz";
|
|
207
357
|
const resolvedCollisionProxy = collisionProxy ?? scenarioEnvironment?.collisionProxy ?? scenario?.splat?.collisionProxy ?? void 0;
|
|
358
|
+
const readiness = useMemo(
|
|
359
|
+
() => getSplatEnvironmentReadiness({
|
|
360
|
+
environment: scenarioEnvironment,
|
|
361
|
+
scenario,
|
|
362
|
+
renderer,
|
|
363
|
+
src: resolvedSrc,
|
|
364
|
+
format: resolvedFormat,
|
|
365
|
+
collisionProxy: resolvedCollisionProxy
|
|
366
|
+
}),
|
|
367
|
+
[
|
|
368
|
+
collisionProxy,
|
|
369
|
+
renderer,
|
|
370
|
+
resolvedCollisionProxy,
|
|
371
|
+
resolvedFormat,
|
|
372
|
+
resolvedSrc,
|
|
373
|
+
scenario,
|
|
374
|
+
scenarioEnvironment
|
|
375
|
+
]
|
|
376
|
+
);
|
|
208
377
|
return useMemo(
|
|
209
378
|
() => ({
|
|
210
379
|
src: resolvedSrc,
|
|
211
380
|
format: resolvedFormat,
|
|
212
381
|
collisionProxy: resolvedCollisionProxy,
|
|
382
|
+
readiness,
|
|
213
383
|
userData: createSplatEnvironmentUserData({
|
|
214
384
|
environment: scenarioEnvironment,
|
|
215
385
|
src: resolvedSrc,
|
|
216
386
|
format: resolvedFormat,
|
|
217
|
-
collisionProxy: resolvedCollisionProxy
|
|
387
|
+
collisionProxy: resolvedCollisionProxy,
|
|
388
|
+
readiness
|
|
218
389
|
})
|
|
219
390
|
}),
|
|
220
|
-
[
|
|
391
|
+
[
|
|
392
|
+
scenarioEnvironment,
|
|
393
|
+
resolvedSrc,
|
|
394
|
+
resolvedFormat,
|
|
395
|
+
resolvedCollisionProxy,
|
|
396
|
+
readiness
|
|
397
|
+
]
|
|
221
398
|
);
|
|
222
399
|
}
|
|
400
|
+
function useSplatSceneConfig({
|
|
401
|
+
sceneConfig,
|
|
402
|
+
scenario,
|
|
403
|
+
environment,
|
|
404
|
+
enabled = true,
|
|
405
|
+
renderer
|
|
406
|
+
}) {
|
|
407
|
+
return useMemo(
|
|
408
|
+
() => createSplatSceneConfig({
|
|
409
|
+
sceneConfig,
|
|
410
|
+
scenario,
|
|
411
|
+
environment,
|
|
412
|
+
enabled,
|
|
413
|
+
renderer
|
|
414
|
+
}),
|
|
415
|
+
[enabled, environment, renderer, scenario, sceneConfig]
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
function createSplatSceneConfig({
|
|
419
|
+
sceneConfig,
|
|
420
|
+
scenario,
|
|
421
|
+
environment,
|
|
422
|
+
enabled = true,
|
|
423
|
+
renderer
|
|
424
|
+
}) {
|
|
425
|
+
const resolvedEnvironment = enabled ? environment ?? (scenario ? createPairedSplatEnvironment(scenario, { renderer }) : void 0) : void 0;
|
|
426
|
+
const readiness = getSplatEnvironmentReadiness({
|
|
427
|
+
environment: resolvedEnvironment,
|
|
428
|
+
scenario,
|
|
429
|
+
renderer,
|
|
430
|
+
enabled
|
|
431
|
+
});
|
|
432
|
+
const resolvedSceneConfig = resolvedEnvironment ? withSplatEnvironment(sceneConfig, resolvedEnvironment, { renderer }) : sceneConfig;
|
|
433
|
+
return {
|
|
434
|
+
environment: resolvedEnvironment,
|
|
435
|
+
sceneConfig: resolvedSceneConfig,
|
|
436
|
+
enabled: enabled && readiness.status !== SplatEnvironmentReadinessStatus.Disabled,
|
|
437
|
+
readiness
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function getSplatEnvironmentReadiness({
|
|
441
|
+
environment,
|
|
442
|
+
scenario,
|
|
443
|
+
renderer,
|
|
444
|
+
src,
|
|
445
|
+
format,
|
|
446
|
+
collisionProxy,
|
|
447
|
+
enabled = true
|
|
448
|
+
}) {
|
|
449
|
+
const splat = scenario?.splat;
|
|
450
|
+
const resolvedSrc = src ?? environment?.splat.src ?? splat?.src;
|
|
451
|
+
const resolvedFormat = format ?? environment?.splat.format ?? splat?.format ?? "spz";
|
|
452
|
+
const resolvedRenderer = renderer ?? environment?.splat.renderer;
|
|
453
|
+
const resolvedCollisionProxy = collisionProxy ?? environment?.collisionProxy ?? splat?.collisionProxy ?? void 0;
|
|
454
|
+
const requiresCollisionProxy = splat?.requiresCollisionProxy ?? true;
|
|
455
|
+
if (!enabled || splat && splat.enabled === false && !environment) {
|
|
456
|
+
return {
|
|
457
|
+
status: SplatEnvironmentReadinessStatus.Disabled,
|
|
458
|
+
ready: false,
|
|
459
|
+
requiresCollisionProxy,
|
|
460
|
+
missing: [],
|
|
461
|
+
format: resolvedFormat,
|
|
462
|
+
renderer: resolvedRenderer,
|
|
463
|
+
message: "Splat environment is disabled."
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
if (!resolvedSrc) {
|
|
467
|
+
return {
|
|
468
|
+
status: SplatEnvironmentReadinessStatus.MissingSplat,
|
|
469
|
+
ready: false,
|
|
470
|
+
requiresCollisionProxy,
|
|
471
|
+
missing: ["splat"],
|
|
472
|
+
format: resolvedFormat,
|
|
473
|
+
renderer: resolvedRenderer,
|
|
474
|
+
message: "Splat environment is missing a visual asset source."
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
if (resolvedRenderer === "spark" && resolvedFormat !== "spz") {
|
|
478
|
+
return {
|
|
479
|
+
status: SplatEnvironmentReadinessStatus.UnsupportedFormat,
|
|
480
|
+
ready: false,
|
|
481
|
+
requiresCollisionProxy,
|
|
482
|
+
missing: [],
|
|
483
|
+
format: resolvedFormat,
|
|
484
|
+
renderer: resolvedRenderer,
|
|
485
|
+
message: `Spark splat rendering requires .spz assets; received ${resolvedFormat}.`
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
if (requiresCollisionProxy && !resolvedCollisionProxy?.xmlPath) {
|
|
489
|
+
return {
|
|
490
|
+
status: SplatEnvironmentReadinessStatus.MissingCollisionProxy,
|
|
491
|
+
ready: false,
|
|
492
|
+
requiresCollisionProxy,
|
|
493
|
+
missing: ["collisionProxy"],
|
|
494
|
+
format: resolvedFormat,
|
|
495
|
+
renderer: resolvedRenderer,
|
|
496
|
+
message: "Splat environment is missing paired MJCF collision proxy XML."
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
status: SplatEnvironmentReadinessStatus.Ready,
|
|
501
|
+
ready: true,
|
|
502
|
+
requiresCollisionProxy,
|
|
503
|
+
missing: [],
|
|
504
|
+
format: resolvedFormat,
|
|
505
|
+
renderer: resolvedRenderer,
|
|
506
|
+
message: requiresCollisionProxy ? "Splat environment has visual asset and collision proxy metadata." : "Splat environment has a visual asset and does not require collision proxy metadata."
|
|
507
|
+
};
|
|
508
|
+
}
|
|
223
509
|
function createPairedSplatEnvironment(scenario, options = {}) {
|
|
224
510
|
const splat = scenario.splat;
|
|
225
511
|
const collisionProxy = splat?.collisionProxy;
|
|
226
|
-
if (!splat?.enabled || !splat.src
|
|
512
|
+
if (!splat?.enabled || !splat.src) {
|
|
227
513
|
return void 0;
|
|
228
514
|
}
|
|
229
515
|
return {
|
|
@@ -235,14 +521,14 @@ function createPairedSplatEnvironment(scenario, options = {}) {
|
|
|
235
521
|
format: splat.format ?? "spz",
|
|
236
522
|
renderer: options.renderer
|
|
237
523
|
},
|
|
238
|
-
collisionProxy: {
|
|
524
|
+
collisionProxy: collisionProxy?.xmlPath ? {
|
|
239
525
|
...collisionProxy,
|
|
240
526
|
xmlPath: collisionProxy.xmlPath
|
|
241
|
-
}
|
|
527
|
+
} : void 0
|
|
242
528
|
};
|
|
243
529
|
}
|
|
244
530
|
function isPairedSplatEnvironment(input) {
|
|
245
|
-
return !!input && "
|
|
531
|
+
return !!input && "splat" in input && !!input.splat && !("enabled" in input.splat);
|
|
246
532
|
}
|
|
247
533
|
function sceneRelativePath(sceneConfig, path) {
|
|
248
534
|
const src = sceneConfig.src;
|
|
@@ -263,7 +549,7 @@ function uniquePaths(paths) {
|
|
|
263
549
|
}
|
|
264
550
|
function withSplatEnvironment(sceneConfig, input, options = {}) {
|
|
265
551
|
const environment = isPairedSplatEnvironment(input) ? input : input ? createPairedSplatEnvironment(input, options) : void 0;
|
|
266
|
-
const xmlPath = environment?.collisionProxy
|
|
552
|
+
const xmlPath = environment?.collisionProxy?.xmlPath;
|
|
267
553
|
if (!xmlPath) return sceneConfig;
|
|
268
554
|
return {
|
|
269
555
|
...sceneConfig,
|
|
@@ -277,7 +563,8 @@ function createSplatEnvironmentUserData({
|
|
|
277
563
|
environment,
|
|
278
564
|
src,
|
|
279
565
|
format = "spz",
|
|
280
|
-
collisionProxy
|
|
566
|
+
collisionProxy,
|
|
567
|
+
readiness
|
|
281
568
|
}) {
|
|
282
569
|
return {
|
|
283
570
|
role: "splat-environment",
|
|
@@ -288,7 +575,9 @@ function createSplatEnvironmentUserData({
|
|
|
288
575
|
splatRenderer: environment?.splat.renderer,
|
|
289
576
|
collisionProxyStatus: collisionProxy?.status ?? "missing",
|
|
290
577
|
collisionProxyXmlPath: collisionProxy?.xmlPath,
|
|
291
|
-
collisionProxyPrimitives: collisionProxy?.primitives ?? []
|
|
578
|
+
collisionProxyPrimitives: collisionProxy?.primitives ?? [],
|
|
579
|
+
readinessStatus: readiness?.status,
|
|
580
|
+
readinessMessage: readiness?.message
|
|
292
581
|
};
|
|
293
582
|
}
|
|
294
583
|
function createSparkSplatViewerUrl({
|
|
@@ -395,6 +684,6 @@ function clamp01(value) {
|
|
|
395
684
|
* SPDX-License-Identifier: Apache-2.0
|
|
396
685
|
*/
|
|
397
686
|
|
|
398
|
-
export { ScenarioLighting, SplatEnvironment, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment, useVisualScenarioEffects, withSplatEnvironment };
|
|
399
|
-
//# sourceMappingURL=chunk-
|
|
400
|
-
//# sourceMappingURL=chunk-
|
|
687
|
+
export { RobotActuators, RobotBodies, RobotCameras, RobotGeoms, RobotJoints, RobotKeyframes, RobotResources, RobotSensors, RobotSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerRobotResources, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
|
|
688
|
+
//# sourceMappingURL=chunk-VDSEPZYQ.js.map
|
|
689
|
+
//# sourceMappingURL=chunk-VDSEPZYQ.js.map
|