mujoco-react 9.3.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 +139 -15
- package/dist/{chunk-T3GVZJ4F.js → chunk-VDSEPZYQ.js} +107 -32
- package/dist/chunk-VDSEPZYQ.js.map +1 -0
- package/dist/index.d.ts +119 -6
- package/dist/index.js +489 -10
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +1 -1
- package/dist/{types-oxbxOkAx.d.ts → types-BuJ4boaq.d.ts} +41 -3
- package/dist/vite.js +8 -4
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SplatCollisionProxyPreview.tsx +350 -0
- package/src/components/VisualScenario.tsx +140 -41
- package/src/core/MujocoSimProvider.tsx +1 -1
- package/src/hooks/useMountedCameraSequenceRecorder.ts +54 -6
- package/src/index.ts +31 -0
- package/src/rendering/cameraFrameSource.ts +372 -0
- package/src/types.ts +44 -2
- package/src/vite.ts +9 -4
- package/dist/chunk-T3GVZJ4F.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,7 +162,9 @@ 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
170
|
import { MujocoCanvas, SplatEnvironment, useSplatSceneConfig } from "mujoco-react";
|
|
@@ -178,13 +180,84 @@ const splat = useSplatSceneConfig({ sceneConfig, scenario });
|
|
|
178
180
|
</MujocoCanvas>;
|
|
179
181
|
```
|
|
180
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);
|
|
217
|
+
```
|
|
218
|
+
|
|
181
219
|
Use `splat.readiness` or `getSplatEnvironmentReadiness(scenario)` to gate
|
|
182
220
|
authoring and import flows. The status distinguishes disabled scenarios,
|
|
183
|
-
missing
|
|
184
|
-
and ready
|
|
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";
|
|
185
230
|
|
|
186
|
-
|
|
187
|
-
|
|
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>`:
|
|
188
261
|
|
|
189
262
|
```tsx
|
|
190
263
|
const sceneConfig = withSplatEnvironment(
|
|
@@ -552,7 +625,7 @@ interface SceneConfig {
|
|
|
552
625
|
}
|
|
553
626
|
```
|
|
554
627
|
|
|
555
|
-
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:
|
|
556
629
|
|
|
557
630
|
```tsx
|
|
558
631
|
const kitchenRobot: SceneConfig = {
|
|
@@ -1066,6 +1139,10 @@ const sequence = await recordMountedCameraFrameSequence(api, {
|
|
|
1066
1139
|
sequence.readiness.ready; // true when every requested stream resolved
|
|
1067
1140
|
sequence.plan.missingKeys; // unresolved task cameras, if requireAll is false
|
|
1068
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"
|
|
1069
1146
|
```
|
|
1070
1147
|
|
|
1071
1148
|
`recordMountedCameraFrameSequence()` requires all requested `cameraKeys` by
|
|
@@ -1073,23 +1150,66 @@ default so dataset recording cannot silently omit a camera stream. Set
|
|
|
1073
1150
|
`requireAll: false` only for exploratory tooling that can tolerate partial
|
|
1074
1151
|
camera coverage.
|
|
1075
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
|
+
|
|
1076
1157
|
Inside `<MujocoCanvas>` children, `useMountedCameraSequenceRecorder()` exposes
|
|
1077
|
-
the same planning and recording surface with React status/error state.
|
|
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.
|
|
1078
1199
|
|
|
1079
1200
|
Use `resolveMountedCameraFrameSource()` when dataset feature names need to map
|
|
1080
|
-
to named MuJoCo cameras, sites, or bodies before recording. The helper
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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:
|
|
1084
1207
|
|
|
1085
1208
|
```tsx
|
|
1086
1209
|
const resolved = resolveMountedCameraFrameSource("head", {
|
|
1087
1210
|
cameras: api.getCameras(),
|
|
1088
1211
|
sites: api.getSites(),
|
|
1089
1212
|
bodies: api.getBodies(),
|
|
1090
|
-
aliases: {
|
|
1091
|
-
head: [{ siteName: "head_camera_rgb_optical_frame" }],
|
|
1092
|
-
},
|
|
1093
1213
|
});
|
|
1094
1214
|
|
|
1095
1215
|
if (!resolved) throw new Error("head does not resolve to a MuJoCo source");
|
|
@@ -1101,6 +1221,10 @@ await api.recordCameraSequence({
|
|
|
1101
1221
|
});
|
|
1102
1222
|
```
|
|
1103
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.
|
|
1227
|
+
|
|
1104
1228
|
### `useCtrlNoise(config)`
|
|
1105
1229
|
|
|
1106
1230
|
Apply Gaussian noise to controls for robustness testing:
|
|
@@ -1,5 +1,5 @@
|
|
|
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
|
|
|
@@ -160,6 +160,75 @@ function getScenarioCameraPosition(basePosition, scenario) {
|
|
|
160
160
|
Number((z + jitter * 0.25).toFixed(3))
|
|
161
161
|
];
|
|
162
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
|
+
}
|
|
163
232
|
function VisualScenarioEffects(props) {
|
|
164
233
|
useVisualScenarioEffects(props);
|
|
165
234
|
return null;
|
|
@@ -335,33 +404,39 @@ function useSplatSceneConfig({
|
|
|
335
404
|
enabled = true,
|
|
336
405
|
renderer
|
|
337
406
|
}) {
|
|
338
|
-
const resolvedEnvironment = useMemo(
|
|
339
|
-
() => enabled ? environment ?? (scenario ? createPairedSplatEnvironment(scenario, { renderer }) : void 0) : void 0,
|
|
340
|
-
[enabled, environment, renderer, scenario]
|
|
341
|
-
);
|
|
342
|
-
const readiness = useMemo(
|
|
343
|
-
() => getSplatEnvironmentReadiness({
|
|
344
|
-
environment: resolvedEnvironment,
|
|
345
|
-
scenario,
|
|
346
|
-
renderer,
|
|
347
|
-
enabled
|
|
348
|
-
}),
|
|
349
|
-
[enabled, renderer, resolvedEnvironment, scenario]
|
|
350
|
-
);
|
|
351
|
-
const resolvedSceneConfig = useMemo(
|
|
352
|
-
() => resolvedEnvironment ? withSplatEnvironment(sceneConfig, resolvedEnvironment) : sceneConfig,
|
|
353
|
-
[resolvedEnvironment, sceneConfig]
|
|
354
|
-
);
|
|
355
407
|
return useMemo(
|
|
356
|
-
() => ({
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
408
|
+
() => createSplatSceneConfig({
|
|
409
|
+
sceneConfig,
|
|
410
|
+
scenario,
|
|
411
|
+
environment,
|
|
412
|
+
enabled,
|
|
413
|
+
renderer
|
|
361
414
|
}),
|
|
362
|
-
[enabled,
|
|
415
|
+
[enabled, environment, renderer, scenario, sceneConfig]
|
|
363
416
|
);
|
|
364
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
|
+
}
|
|
365
440
|
function getSplatEnvironmentReadiness({
|
|
366
441
|
environment,
|
|
367
442
|
scenario,
|
|
@@ -434,7 +509,7 @@ function getSplatEnvironmentReadiness({
|
|
|
434
509
|
function createPairedSplatEnvironment(scenario, options = {}) {
|
|
435
510
|
const splat = scenario.splat;
|
|
436
511
|
const collisionProxy = splat?.collisionProxy;
|
|
437
|
-
if (!splat?.enabled || !splat.src
|
|
512
|
+
if (!splat?.enabled || !splat.src) {
|
|
438
513
|
return void 0;
|
|
439
514
|
}
|
|
440
515
|
return {
|
|
@@ -446,14 +521,14 @@ function createPairedSplatEnvironment(scenario, options = {}) {
|
|
|
446
521
|
format: splat.format ?? "spz",
|
|
447
522
|
renderer: options.renderer
|
|
448
523
|
},
|
|
449
|
-
collisionProxy: {
|
|
524
|
+
collisionProxy: collisionProxy?.xmlPath ? {
|
|
450
525
|
...collisionProxy,
|
|
451
526
|
xmlPath: collisionProxy.xmlPath
|
|
452
|
-
}
|
|
527
|
+
} : void 0
|
|
453
528
|
};
|
|
454
529
|
}
|
|
455
530
|
function isPairedSplatEnvironment(input) {
|
|
456
|
-
return !!input && "
|
|
531
|
+
return !!input && "splat" in input && !!input.splat && !("enabled" in input.splat);
|
|
457
532
|
}
|
|
458
533
|
function sceneRelativePath(sceneConfig, path) {
|
|
459
534
|
const src = sceneConfig.src;
|
|
@@ -474,7 +549,7 @@ function uniquePaths(paths) {
|
|
|
474
549
|
}
|
|
475
550
|
function withSplatEnvironment(sceneConfig, input, options = {}) {
|
|
476
551
|
const environment = isPairedSplatEnvironment(input) ? input : input ? createPairedSplatEnvironment(input, options) : void 0;
|
|
477
|
-
const xmlPath = environment?.collisionProxy
|
|
552
|
+
const xmlPath = environment?.collisionProxy?.xmlPath;
|
|
478
553
|
if (!xmlPath) return sceneConfig;
|
|
479
554
|
return {
|
|
480
555
|
...sceneConfig,
|
|
@@ -609,6 +684,6 @@ function clamp01(value) {
|
|
|
609
684
|
* SPDX-License-Identifier: Apache-2.0
|
|
610
685
|
*/
|
|
611
686
|
|
|
612
|
-
export { RobotActuators, RobotBodies, RobotCameras, RobotGeoms, RobotJoints, RobotKeyframes, RobotResources, RobotSensors, RobotSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerRobotResources, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, withContacts, withSplatEnvironment };
|
|
613
|
-
//# sourceMappingURL=chunk-
|
|
614
|
-
//# 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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/components/VisualScenario.tsx"],"names":[],"mappings":"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","file":"chunk-VDSEPZYQ.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type React from 'react';\nimport type { ReactNode } from 'react';\nimport type { CanvasProps, ThreeElements } from '@react-three/fiber';\nimport * as THREE from 'three';\n\n// ---- Register (type-safe named resources) ----\n\n/**\n * Module augmentation interface for type-safe resource names.\n *\n * Declare your model's resource names via module augmentation:\n * ```ts\n * declare module 'mujoco-react' {\n * interface Register {\n * robots: {\n * panda: {\n * actuators: 'joint1' | 'joint2' | 'gripper';\n * sensors: 'force_sensor' | 'torque_sensor';\n * bodies: 'link0' | 'link1' | 'hand';\n * };\n * };\n * actuators: 'joint1' | 'joint2' | 'gripper';\n * sensors: 'force_sensor' | 'torque_sensor';\n * bodies: 'link0' | 'link1' | 'hand';\n * }\n * }\n * ```\n *\n * When no augmentation is declared, all names fall back to `string`.\n */\nexport interface Register {}\n\nexport type RegisteredRobotMap = Register extends { robots: infer T extends Record<string, Record<string, string>> }\n ? T\n : never;\nexport type Robots = [RegisteredRobotMap] extends [never] ? string : Extract<keyof RegisteredRobotMap, string>;\nexport type RobotResource<TRobot extends string, TKey extends string> =\n [RegisteredRobotMap] extends [never]\n ? string\n : TRobot extends keyof RegisteredRobotMap\n ? TKey extends keyof RegisteredRobotMap[TRobot]\n ? RegisteredRobotMap[TRobot][TKey]\n : string\n : never;\nexport type RobotActuators<TRobot extends string> = RobotResource<TRobot, 'actuators'>;\nexport type RobotSensors<TRobot extends string> = RobotResource<TRobot, 'sensors'>;\nexport type RobotBodies<TRobot extends string> = RobotResource<TRobot, 'bodies'>;\nexport type RobotJoints<TRobot extends string> = RobotResource<TRobot, 'joints'>;\nexport type RobotSites<TRobot extends string> = RobotResource<TRobot, 'sites'>;\nexport type RobotGeoms<TRobot extends string> = RobotResource<TRobot, 'geoms'>;\nexport type RobotKeyframes<TRobot extends string> = RobotResource<TRobot, 'keyframes'>;\nexport type RobotCameras<TRobot extends string> = RobotResource<TRobot, 'cameras'>;\n\nexport type RegisterResourceKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes' | 'cameras';\nexport type RobotResourceObject<TRobot extends string, TKey extends RegisterResourceKey> =\n string extends RobotResource<TRobot, TKey>\n ? Record<string, string>\n : { readonly [K in RobotResource<TRobot, TKey>]: K };\nexport type RobotResourceCategory<TKey extends RegisterResourceKey> =\n string extends Robots\n ? Record<string, Record<string, string>>\n : { readonly [TRobot in Robots]: RobotResourceObject<TRobot, TKey> };\nexport type RobotResourceRegistry =\n string extends Robots\n ? Record<string, Record<RegisterResourceKey, Record<string, string>>>\n : { readonly [TRobot in Robots]: { readonly [TKey in RegisterResourceKey]: RobotResourceObject<TRobot, TKey> } };\n\ntype RuntimeRobotResources = Record<string, Record<RegisterResourceKey, Record<string, string>>>;\ntype RuntimeRobotResourceRegistration = Readonly<Record<string, Readonly<Record<RegisterResourceKey, Readonly<Record<string, string>>>>>>;\n\nconst runtimeRobotResources: RuntimeRobotResources = {};\nconst REGISTER_RESOURCE_KEYS: RegisterResourceKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes', 'cameras'];\n\nfunction createEmptyRuntimeResources(): Record<RegisterResourceKey, Record<string, string>> {\n return {\n actuators: {},\n sensors: {},\n bodies: {},\n joints: {},\n sites: {},\n geoms: {},\n keyframes: {},\n cameras: {},\n };\n}\n\nexport function registerRobotResources(resources: RuntimeRobotResourceRegistration): void {\n for (const [robot, robotResources] of Object.entries(resources)) {\n const existing = runtimeRobotResources[robot] ?? createEmptyRuntimeResources();\n for (const key of REGISTER_RESOURCE_KEYS) {\n existing[key] = { ...existing[key], ...(robotResources[key] ?? {}) };\n }\n runtimeRobotResources[robot] = existing;\n }\n}\n\nfunction createResourceCategory<TKey extends RegisterResourceKey>(key: TKey): RobotResourceCategory<TKey> {\n return new Proxy({}, {\n get(_target, robot) {\n if (typeof robot !== 'string') return undefined;\n return runtimeRobotResources[robot]?.[key] ?? {};\n },\n ownKeys() {\n return Reflect.ownKeys(runtimeRobotResources);\n },\n getOwnPropertyDescriptor(_target, robot) {\n if (typeof robot !== 'string' || !(robot in runtimeRobotResources)) return undefined;\n return { enumerable: true, configurable: true };\n },\n }) as RobotResourceCategory<TKey>;\n}\n\nexport const RobotResources: RobotResourceRegistry = new Proxy(runtimeRobotResources, {\n get(target, robot) {\n if (typeof robot !== 'string') return undefined;\n return target[robot] ?? createEmptyRuntimeResources();\n },\n ownKeys(target) {\n return Reflect.ownKeys(target);\n },\n getOwnPropertyDescriptor(target, robot) {\n if (typeof robot !== 'string' || !(robot in target)) return undefined;\n return { enumerable: true, configurable: true };\n },\n}) as RobotResourceRegistry;\n\nexport const RobotActuators: RobotResourceCategory<'actuators'> = createResourceCategory('actuators');\nexport const RobotSensors: RobotResourceCategory<'sensors'> = createResourceCategory('sensors');\nexport const RobotBodies: RobotResourceCategory<'bodies'> = createResourceCategory('bodies');\nexport const RobotJoints: RobotResourceCategory<'joints'> = createResourceCategory('joints');\nexport const RobotSites: RobotResourceCategory<'sites'> = createResourceCategory('sites');\nexport const RobotGeoms: RobotResourceCategory<'geoms'> = createResourceCategory('geoms');\nexport const RobotKeyframes: RobotResourceCategory<'keyframes'> = createResourceCategory('keyframes');\nexport const RobotCameras: RobotResourceCategory<'cameras'> = createResourceCategory('cameras');\n\nexport type Actuators = Register extends { actuators: infer T extends string } ? T : string;\nexport type Sensors = Register extends { sensors: infer T extends string } ? T : string;\nexport type Bodies = Register extends { bodies: infer T extends string } ? T : string;\nexport type Joints = Register extends { joints: infer T extends string } ? T : string;\nexport type Sites = Register extends { sites: infer T extends string } ? T : string;\nexport type Geoms = Register extends { geoms: infer T extends string } ? T : string;\nexport type Keyframes = Register extends { keyframes: infer T extends string } ? T : string;\nexport type Cameras = Register extends { cameras: infer T extends string } ? T : string;\n\n// ---- MuJoCo WASM Types ----\n\n/**\n * A single MuJoCo contact from the WASM module.\n * Accessed via `data.contact.get(i)`.\n */\nexport interface MujocoContact {\n geom1: number;\n geom2: number;\n pos: Float64Array;\n frame: Float64Array;\n dist: number;\n}\n\n/**\n * WASM contact array — supports indexed access via `.get(i)`.\n */\nexport interface MujocoContactArray {\n get(i: number): MujocoContact | undefined;\n delete?: () => void;\n}\n\n/**\n * Read a single contact from an already-acquired WASM contact array.\n * Returns undefined if the access fails (WASM heap issue, bad index, etc.).\n */\nexport function getContact(contacts: MujocoContactArray, i: number): MujocoContact | undefined {\n try {\n return contacts.get(i);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Access the current contact vector and release the copied WASM handle afterwards.\n */\nexport function withContacts<T>(data: MujocoData, read: (contacts: MujocoContactArray) => T): T {\n const contacts = data.contact;\n try {\n return read(contacts);\n } finally {\n contacts.delete?.();\n }\n}\n\n/**\n * Minimal interface for MuJoCo Model to avoid 'any'.\n */\nexport interface MujocoModel {\n // Counts\n nbody: number;\n ngeom: number;\n nsite: number;\n nu: number;\n njnt: number;\n nq: number;\n nv: number;\n nkey: number;\n nsensor: number;\n nsensordata: number;\n nlight: number;\n ntendon: number;\n nflex: number;\n nmesh: number;\n nmat: number;\n ncam?: number;\n\n // Name tables\n names: Int8Array;\n name_bodyadr: Int32Array;\n name_jntadr: Int32Array;\n name_geomadr: Int32Array;\n name_siteadr: Int32Array;\n name_actuatoradr: Int32Array;\n name_keyadr: Int32Array;\n name_sensoradr: Int32Array;\n name_tendonadr: Int32Array;\n name_camadr?: Int32Array;\n\n // Body\n body_mass: Float64Array;\n body_parentid: Int32Array;\n body_jntnum: Int32Array;\n body_jntadr: Int32Array;\n body_pos: Float64Array;\n body_quat: Float64Array;\n body_geomnum: Int32Array;\n body_geomadr: Int32Array;\n body_inertia: Float64Array;\n\n // Default configuration\n qpos0: Float64Array;\n\n // Joint\n jnt_qposadr: Int32Array;\n jnt_dofadr: Int32Array;\n jnt_type: Int32Array;\n jnt_range: Float64Array;\n jnt_bodyid: Int32Array;\n jnt_pos: Float64Array;\n jnt_axis: Float64Array;\n jnt_limited: Uint8Array;\n\n // Geom\n geom_group: Int32Array;\n geom_type: Int32Array;\n geom_size: Float64Array;\n geom_pos: Float64Array;\n geom_quat: Float64Array;\n geom_matid: Int32Array;\n geom_rgba: Float32Array;\n geom_dataid: Int32Array;\n geom_bodyid: Int32Array;\n geom_contype: Int32Array;\n geom_conaffinity: Int32Array;\n geom_friction: Float64Array;\n\n // Material\n mat_rgba: Float32Array;\n\n // Mesh\n mesh_vertadr: Int32Array;\n mesh_vertnum: Int32Array;\n mesh_faceadr: Int32Array;\n mesh_facenum: Int32Array;\n mesh_vert: Float32Array;\n mesh_face: Int32Array;\n mesh_normal: Float32Array;\n\n // Site\n site_bodyid: Int32Array;\n\n // Actuator\n actuator_trnid: Int32Array;\n actuator_ctrlrange: Float64Array;\n actuator_trntype: Int32Array;\n actuator_gainprm: Float64Array;\n actuator_biasprm: Float64Array;\n\n // Sensor\n sensor_type: Int32Array;\n sensor_dim: Int32Array;\n sensor_adr: Int32Array;\n sensor_objtype: Int32Array;\n sensor_objid: Int32Array;\n\n // Keyframe\n key_qpos: Float64Array;\n key_ctrl: Float64Array;\n key_time: Float64Array;\n key_qvel: Float64Array;\n\n // Light\n light_pos: Float64Array;\n light_dir: Float64Array;\n light_diffuse: Float32Array;\n light_specular: Float32Array;\n light_type: Int32Array;\n light_active: Uint8Array;\n light_castshadow: Uint8Array;\n light_attenuation: Float32Array;\n light_cutoff: Float32Array;\n light_exponent: Float32Array;\n light_intensity: Float32Array;\n\n // Camera\n cam_bodyid?: Int32Array;\n cam_pos?: Float64Array;\n cam_quat?: Float64Array;\n cam_fovy?: Float64Array;\n\n // Tendon\n ten_wrapadr: Int32Array;\n ten_wrapnum: Int32Array;\n ten_range: Float64Array;\n ten_rgba: Float32Array;\n ten_width: Float64Array;\n\n // Flex\n flex_vertadr: Int32Array;\n flex_vertnum: Int32Array;\n flex_faceadr: Int32Array;\n flex_facenum: Int32Array;\n flex_face: Int32Array;\n flex_rgba: Float32Array;\n\n // Model options\n opt: {\n timestep: number;\n gravity: Float64Array;\n integrator: number;\n [key: string]: unknown;\n };\n\n delete: () => void;\n [key: string]: unknown;\n}\n\n/**\n * Minimal interface for MuJoCo Data to avoid 'any'.\n */\nexport interface MujocoData {\n time: number;\n qpos: Float64Array;\n qvel: Float64Array;\n ctrl: Float64Array;\n act: Float64Array;\n xpos: Float64Array;\n xquat: Float64Array;\n xfrc_applied: Float64Array;\n qfrc_applied: Float64Array;\n qfrc_bias: Float64Array;\n site_xpos: Float64Array;\n site_xmat: Float64Array;\n cam_xpos?: Float64Array;\n cam_xmat?: Float64Array;\n xmat?: Float64Array;\n sensordata: Float64Array;\n ncon: number;\n contact: MujocoContactArray;\n cvel: Float64Array;\n cfrc_ext: Float64Array;\n ten_length: Float64Array;\n wrap_xpos: Float64Array;\n ten_wrapadr: Int32Array;\n flexvert_xpos: Float64Array;\n geom_xpos: Float64Array;\n geom_xmat: Float64Array;\n delete: () => void;\n [key: string]: unknown;\n}\n\n/**\n * Minimal interface for the MuJoCo WASM Module.\n */\nexport interface MujocoModule {\n MjModel: {\n from_xml_path?: (path: string) => MujocoModel;\n from_xml_string?: (xml: string, vfs?: unknown) => MujocoModel;\n loadFromXML?: (path: string) => MujocoModel;\n [key: string]: unknown;\n };\n MjData: new (model: MujocoModel) => MujocoData;\n MjvOption: new () => { delete: () => void; [key: string]: unknown };\n mj_forward: (m: MujocoModel, d: MujocoData) => void;\n mj_step: (m: MujocoModel, d: MujocoData) => void;\n mj_resetData: (m: MujocoModel, d: MujocoData) => void;\n mj_step1: (m: MujocoModel, d: MujocoData) => void;\n mj_step2: (m: MujocoModel, d: MujocoData) => void;\n mj_applyFT: (\n model: MujocoModel,\n data: MujocoData,\n force: Float64Array,\n torque: Float64Array,\n point: Float64Array,\n bodyId: number,\n qfrc_target: Float64Array\n ) => void;\n mj_ray: (\n model: MujocoModel,\n data: MujocoData,\n pnt: Float64Array,\n vec: Float64Array,\n geomgroup: Uint8Array | null,\n flg_static: number,\n bodyexclude: number,\n geomid: Int32Array\n ) => number;\n mj_name2id: (model: MujocoModel, type: number, name: string) => number;\n mjtObj: Record<string, number>;\n mjtGeom: Record<string, number | {value: number}>;\n mjtJoint: Record<string, number | {value: number}>;\n mjtSensor: Record<string, number | {value: number}>;\n FS: {\n writeFile: (path: string, content: string | Uint8Array) => void;\n readFile: (path: string, opts?: { encoding: string }) => string | Uint8Array;\n mkdir: (path: string) => void;\n unmount: (path: string) => void;\n };\n [key: string]: unknown;\n}\n\n// ---- Scene Configuration ----\n\nexport interface SceneObject {\n name: string;\n type: 'box' | 'sphere' | 'cylinder';\n size: [number, number, number];\n position: [number, number, number];\n rgba: [number, number, number, number];\n mass?: number;\n freejoint?: boolean;\n friction?: string;\n solref?: string;\n solimp?: string;\n condim?: number;\n /** MuJoCo geom group. Group 3 is conventionally used for collision-only helper geoms. */\n group?: number;\n}\n\nexport interface XmlPatch {\n target: string;\n inject?: string;\n injectAfter?: string;\n replace?: [string, string];\n}\n\nexport type LocalMujocoFile = File;\n\nexport interface LoadFromFilesOptions {\n /** Entry MJCF/URDF file. Inferred from scene.xml, model.xml, robot.xml, or the first XML/URDF file when omitted. */\n sceneFile?: string;\n /** Additional MJCF environment XML files merged into the entry scene before MuJoCo compilation. */\n environmentFiles?: string[];\n homeJoints?: number[];\n xmlPatches?: XmlPatch[];\n sceneObjects?: SceneObject[];\n onReset?: (input: ResetCallbackInput) => void;\n}\n\nexport interface SceneConfig {\n /** Base URL for fetching model files. The loader fetches `src + sceneFile` and follows dependencies. */\n src: string;\n /** Entry MJCF XML or URDF file name, e.g. 'scene.xml' or 'robot.urdf'. */\n sceneFile: string;\n /** Browser-selected files for local MJCF/URDF loading. Preserves webkitRelativePath when available. */\n files?: readonly LocalMujocoFile[];\n /**\n * Additional MJCF environment XML files merged into the entry scene before compilation.\n *\n * Use this for static collision/physics layers such as a Gaussian-splat\n * environment's proxy `scene.xml`; render the splat itself as a separate\n * visual layer.\n */\n environmentFiles?: string[];\n sceneObjects?: SceneObject[];\n homeJoints?: number[];\n xmlPatches?: XmlPatch[];\n onReset?: (input: ResetCallbackInput) => void;\n}\n\n// ---- IK Controller Config ----\n\nexport type ResourceSelector<TInfo, TName extends string = string> =\n | TName\n | readonly TName[]\n | RegExp\n | ((info: TInfo) => boolean);\n\nexport interface IkConfig {\n /** MuJoCo site name for IK target. */\n siteName: Sites;\n /**\n * Explicit joints for IK. When omitted, the controller infers scalar hinge/slide\n * joints by walking from the site body to the model root.\n */\n joints?: ResourceSelector<JointInfo, Joints>;\n /** Explicit actuators for IK control output. */\n actuators?: ResourceSelector<ActuatorInfo, Actuators>;\n /**\n * Number of joints to solve for, assuming legacy contiguous qpos/ctrl layout\n * starting at index 0. Prefer inferred IK or `joints`/`actuators`.\n */\n numJoints?: number;\n /** Custom IK solver. When omitted, uses built-in Damped Least-Squares solver. */\n ikSolveFn?: IKSolveFn;\n /** DLS damping. Default: 0.01. */\n damping?: number;\n /** Max solver iterations. Default: 50. */\n maxIterations?: number;\n}\n\nexport interface IkContextValue {\n ikEnabledRef: React.RefObject<boolean>;\n ikCalculatingRef: React.RefObject<boolean>;\n ikTargetRef: React.RefObject<THREE.Group>;\n siteIdRef: React.RefObject<number>;\n setIkEnabled: (enabled: boolean) => void;\n moveTarget: (pos: THREE.Vector3, duration?: number) => void;\n syncTargetToSite: () => void;\n solveIK: (input: IkSolveInput) => number[] | null;\n getGizmoStats: () => { pos: THREE.Vector3; rot: THREE.Euler } | null;\n}\n\nexport interface SceneMarker {\n id: number;\n position: THREE.Vector3;\n label: string;\n}\n\n// ---- Physics Config (spec 1.1) ----\n\nexport interface PhysicsConfig {\n gravity?: [number, number, number];\n timestep?: number;\n substeps?: number;\n paused?: boolean;\n speed?: number;\n}\n\n// ---- IK ----\n\nexport type IKSolveFn = (\n input: IkSolveInput\n) => number[] | null;\n\nexport interface IkSolveInput {\n position: THREE.Vector3;\n quaternion: THREE.Quaternion;\n currentQ: number[];\n context?: IKSolveContext;\n}\n\nexport interface IKSolveContext {\n model: MujocoModel;\n data: MujocoData;\n siteId: number;\n controlGroup: ControlGroupInfo;\n}\n\n// ---- Callbacks ----\n\nexport interface PhysicsStepInput {\n model: MujocoModel;\n data: MujocoData;\n}\n\nexport interface ResetCallbackInput extends PhysicsStepInput {}\n\nexport interface ReadyCallbackInput {\n api: MujocoSimAPI;\n}\n\nexport interface StepCallbackInput {\n time: number;\n model: MujocoModel;\n data: MujocoData;\n}\n\nexport interface SelectionCallbackInput {\n bodyId: number;\n name: string;\n}\n\nexport type PhysicsStepCallback = (input: PhysicsStepInput) => void;\n\n// ---- State Management (spec 4.1) ----\n\nexport interface StateSnapshot {\n time: number;\n qpos: Float64Array;\n qvel: Float64Array;\n ctrl: Float64Array;\n act: Float64Array;\n qfrc_applied: Float64Array;\n}\n\n// ---- Model Introspection (spec 5.1) ----\n\nexport interface BodyInfo {\n id: number;\n name: string;\n mass: number;\n parentId: number;\n}\n\nexport interface JointInfo {\n id: number;\n name: string;\n type: number;\n typeName: string;\n range: [number, number];\n limited: boolean;\n bodyId: number;\n qposAdr: number;\n dofAdr: number;\n}\n\nexport interface GeomInfo {\n id: number;\n name: string;\n type: number;\n typeName: string;\n size: [number, number, number];\n bodyId: number;\n}\n\nexport interface SiteInfo {\n id: number;\n name: string;\n bodyId: number;\n}\n\nexport interface ActuatorInfo {\n id: number;\n name: string;\n range: [number, number];\n}\n\nexport interface ActuatedJointInfo extends JointInfo {\n actuatorId: number;\n actuatorName: string;\n ctrlAdr: number;\n ctrlRange: [number, number];\n}\n\nexport interface ControlJointInfo extends JointInfo {\n actuatorId: number | null;\n actuatorName: string | null;\n ctrlAdr: number | null;\n ctrlRange: [number, number] | null;\n}\n\nexport interface ControlGroupSelector {\n /** Infer a kinematic chain from a MuJoCo site. */\n siteName?: Sites;\n /** Infer a kinematic chain from a body. */\n bodyName?: Bodies;\n /** Select joints by name, names, regex, or predicate. */\n joints?: ResourceSelector<JointInfo, Joints>;\n /** Select actuators by name, names, regex, or predicate. */\n actuators?: ResourceSelector<ActuatorInfo, Actuators>;\n}\n\nexport interface ControlGroupInfo {\n /** Joints in solve/control order. */\n joints: ControlJointInfo[];\n /** Actuators in control output order. */\n actuators: ActuatorInfo[];\n /** qpos addresses for scalar hinge/slide joints. */\n qposAdr: number[];\n /** dof addresses for scalar hinge/slide joints. */\n dofAdr: number[];\n /** ctrl addresses matching writable actuators. */\n ctrlAdr: number[];\n readQpos(data: MujocoData): Float64Array;\n readCtrl(data: MujocoData): Float64Array;\n writeQpos(data: MujocoData, values: ArrayLike<number>): void;\n writeCtrl(data: MujocoData, values: ArrayLike<number>): void;\n}\n\nexport interface SensorInfo {\n id: number;\n name: string;\n type: number;\n typeName: string;\n dim: number;\n adr: number;\n}\n\nexport interface CameraInfo {\n id: number;\n name: string;\n bodyId: number;\n fov: number | null;\n position: [number, number, number] | null;\n quaternion: [number, number, number, number] | null;\n}\n\n// ---- Contacts (spec 2.4, 2.5) ----\n\nexport interface ContactInfo {\n geom1: number;\n geom1Name: string;\n geom2: number;\n geom2Name: string;\n pos: [number, number, number];\n depth: number;\n}\n\n// ---- Raycast (spec 7.1) ----\n\nexport interface RayHit {\n point: THREE.Vector3;\n bodyId: number;\n geomId: number;\n distance: number;\n}\n\n// ---- Model Options (spec 5.3) ----\n\nexport interface ModelOptions {\n timestep: number;\n gravity: [number, number, number];\n integrator: number;\n}\n\n// ---- Trajectory (spec 13.1, 13.2) ----\n\nexport interface TrajectoryFrame {\n time: number;\n qpos: Float64Array;\n qvel?: Float64Array;\n ctrl?: Float64Array;\n sensordata?: Float64Array;\n}\n\nexport interface TrajectoryData {\n frames: TrajectoryFrame[];\n fps: number;\n}\n\nexport type PlaybackState = 'idle' | 'playing' | 'paused' | 'completed';\n\n// ---- Keyboard Teleop (spec 12.1) ----\n\nexport interface KeyBinding {\n actuator: Actuators;\n delta?: number;\n toggle?: [number, number];\n set?: number;\n}\n\nexport interface KeyboardTeleopConfig {\n bindings: Record<string, KeyBinding>;\n enabled?: boolean;\n}\n\n// ---- Policy (spec 10.1) ----\n\nexport type PolicyVector = Float32Array | Float64Array | number[];\n\nexport interface PolicyObservationInput {\n model: MujocoModel;\n data: MujocoData;\n}\n\nexport interface PolicyInferenceInput extends PolicyObservationInput {\n observation: PolicyVector;\n}\n\nexport interface PolicyActionInput extends PolicyInferenceInput {\n action: PolicyVector;\n}\n\nexport interface PolicyConfig {\n frequency: number;\n enabled?: boolean;\n onObservation: (input: PolicyObservationInput) => PolicyVector;\n /** Run policy inference. Omit to pass observations directly to `onAction` for custom inline controllers. */\n infer?: (input: PolicyInferenceInput) => PolicyVector;\n onAction: (input: PolicyActionInput) => void;\n}\n\n// ---- Observation Builder ----\n\nexport type ObservationOutput = 'float32' | 'float64';\n\nexport interface ObservationConfig {\n /** Include scalar simulation time. */\n time?: boolean;\n /** Include all qpos values. */\n qpos?: boolean;\n /** Include all qvel values. */\n qvel?: boolean;\n /** Include all ctrl values. */\n ctrl?: boolean;\n /** Include all actuator activation values. */\n act?: boolean;\n /** Include all raw sensordata values. */\n sensordata?: boolean;\n /** Include named sensor values in the configured order. */\n sensors?: readonly Sensors[];\n /** Include named site world positions in the configured order. */\n sites?: readonly Sites[];\n /** Include world gravity projected into each named body's local frame. */\n projectedGravity?: Bodies | readonly Bodies[];\n /** Output array type. Defaults to Float32Array. */\n output?: ObservationOutput;\n}\n\nexport interface ObservationLayoutItem {\n name: string;\n start: number;\n size: number;\n}\n\nexport interface ObservationResult {\n values: Float32Array | Float64Array;\n layout: ObservationLayoutItem[];\n}\n\nexport interface ObservationHandle {\n /** Read a fresh observation from the current live MuJoCo model/data refs. */\n read(): ObservationResult;\n /** Read just the vector values for policy inference. */\n readValues(): Float32Array | Float64Array;\n}\n\n// ---- Debug Component (spec 6.1) ----\n\nexport interface DebugProps {\n showGeoms?: boolean;\n showSites?: boolean;\n showJoints?: boolean;\n showContacts?: boolean;\n showCOM?: boolean;\n showInertia?: boolean;\n showTendons?: boolean;\n}\n\n// ---- Component Props ----\n\nexport interface IkGizmoProps {\n controller: IkContextValue;\n siteName?: string;\n scale?: number;\n onDrag?: (input: IkGizmoDragInput) => void;\n}\n\nexport interface IkGizmoDragInput {\n position: THREE.Vector3;\n quaternion: THREE.Quaternion;\n}\n\nexport interface DragInteractionProps {\n stiffness?: number;\n showArrow?: boolean;\n}\n\nexport interface SceneLightsProps {\n /** Override intensity for all MJCF lights. Default: 1.0. */\n intensity?: number;\n}\n\n// ---- Visual scenarios / 3DGS composition ----\n\nexport type ScenarioLightingPreset = 'studio' | 'warehouse' | 'low-light' | 'splat';\nexport type SplatFormat = 'spz' | 'ply' | 'splat';\nexport type SplatRendererKind = 'spark' | 'custom';\nexport type SplatCollisionPrimitive = 'plane' | 'box' | 'sphere' | 'capsule' | 'mesh';\n\nexport interface ScenarioCameraConfig {\n jitter?: number;\n exposure?: number;\n noise?: number;\n blur?: number;\n}\n\nexport interface ScenarioMaterialConfig {\n randomizeObjectColors?: boolean;\n randomizeTableMaterial?: boolean;\n roughness?: number;\n metalness?: number;\n}\n\nexport interface SplatAssetConfig {\n src: string;\n /** Common browser-friendly splat format. Renderer-specific loaders may accept more. */\n format?: SplatFormat;\n /** Optional renderer hint. The library does not import renderer-specific code. */\n renderer?: SplatRendererKind;\n}\n\nexport interface SplatScenarioConfig {\n enabled: boolean;\n /** Common browser-friendly splat format. Renderer-specific loaders may accept more. */\n format?: SplatFormat;\n src?: string;\n requiresCollisionProxy?: boolean;\n collisionProxy?: SplatCollisionProxyConfig | null;\n}\n\nexport interface SplatCollisionProxyConfig {\n /** MJCF/XML file or artifact path that provides physics collision for the visual splat. */\n xmlPath?: string;\n /** Human-readable status for authoring and validation flows. */\n status?: 'missing' | 'planned' | 'generated' | 'validated';\n /** Primitive proxy shapes expected in the MJCF collision proxy. */\n primitives?: SplatCollisionPrimitive[];\n /** Optional notes that should travel with scene variants and rollout metadata. */\n notes?: string[];\n}\n\nexport interface PairedSplatEnvironmentConfig {\n id: string;\n label: string;\n description?: string;\n /** Visual-only Gaussian splat asset. */\n splat: SplatAssetConfig;\n /** Optional MJCF/XML contact geometry paired with the visual splat. */\n collisionProxy?: SplatCollisionProxyConfig & { xmlPath: string };\n}\n\nexport const SplatEnvironmentReadinessStatus = {\n Disabled: 'disabled',\n MissingSplat: 'missing-splat',\n MissingCollisionProxy: 'missing-collision-proxy',\n UnsupportedFormat: 'unsupported-format',\n Ready: 'ready',\n} as const;\n\nexport type SplatEnvironmentReadinessStatus =\n (typeof SplatEnvironmentReadinessStatus)[keyof typeof SplatEnvironmentReadinessStatus];\n\nexport interface SplatEnvironmentReadiness {\n status: SplatEnvironmentReadinessStatus;\n ready: boolean;\n requiresCollisionProxy: boolean;\n missing: Array<'splat' | 'collisionProxy'>;\n format?: SplatFormat;\n renderer?: SplatRendererKind;\n message: string;\n}\n\nexport interface SplatEnvironmentMetadataInput {\n environment?: PairedSplatEnvironmentConfig;\n scenario?: VisualScenarioConfig;\n renderer?: SplatRendererKind;\n src?: string;\n format?: SplatFormat;\n collisionProxy?: SplatCollisionProxyConfig;\n}\n\nexport interface SplatEnvironmentMetadata {\n src?: string;\n format: SplatFormat;\n collisionProxy?: SplatCollisionProxyConfig;\n readiness: SplatEnvironmentReadiness;\n userData: Record<string, unknown>;\n}\n\nexport interface ResolvedScenarioCameraConfig {\n jitter: number;\n exposure: number;\n noise: number;\n blur: number;\n}\n\nexport interface ResolvedScenarioMaterialConfig {\n randomizeObjectColors: boolean;\n randomizeTableMaterial: boolean;\n roughness?: number;\n metalness?: number;\n}\n\nexport interface VisualScenarioExecutionContext {\n scenarioId: string;\n scenarioLabel: string;\n variantId?: string;\n seed: number;\n lighting: ScenarioLightingPreset;\n environment?: string;\n camera: ResolvedScenarioCameraConfig;\n materials: ResolvedScenarioMaterialConfig;\n splatEnabled: boolean;\n splatSrc?: string;\n splatFormat: SplatFormat;\n splatRenderer?: SplatRendererKind;\n collisionProxyXmlPath?: string;\n collisionProxyStatus?: SplatCollisionProxyConfig['status'];\n collisionProxyPrimitives: SplatCollisionPrimitive[];\n readiness: SplatEnvironmentReadiness;\n transformSource: 'visualScenario.camera';\n}\n\nexport interface VisualScenarioExecutionContextInput {\n scenario?: VisualScenarioConfig;\n environment?: PairedSplatEnvironmentConfig;\n renderer?: SplatRendererKind;\n variantId?: string;\n enabled?: boolean;\n}\n\nexport type SplatSceneInput =\n | PairedSplatEnvironmentConfig\n | VisualScenarioConfig\n | undefined\n | null;\n\nexport interface SplatSceneConfigInput {\n sceneConfig: SceneConfig;\n scenario?: VisualScenarioConfig;\n environment?: PairedSplatEnvironmentConfig;\n enabled?: boolean;\n renderer?: SplatRendererKind;\n}\n\nexport interface SplatSceneConfigState {\n environment: PairedSplatEnvironmentConfig | undefined;\n sceneConfig: SceneConfig;\n enabled: boolean;\n readiness: SplatEnvironmentReadiness;\n}\n\nexport interface VisualScenarioConfig {\n id?: string;\n label?: string;\n seed?: number;\n lighting?: ScenarioLightingPreset;\n environment?: string;\n camera?: ScenarioCameraConfig;\n materials?: ScenarioMaterialConfig;\n splat?: SplatScenarioConfig | null;\n}\n\nexport interface ScenarioLightingProps {\n preset?: ScenarioLightingPreset;\n intensity?: number;\n castShadow?: boolean;\n}\n\nexport interface SplatEnvironmentProps extends Omit<ThreeElements['group'], 'ref'> {\n environment?: PairedSplatEnvironmentConfig;\n scenario?: VisualScenarioConfig;\n renderer?: SplatRendererKind;\n src?: string;\n format?: SplatFormat;\n collisionProxy?: ReactNode;\n collisionProxyMetadata?: SplatCollisionProxyConfig;\n showPlaceholder?: boolean;\n}\n\nexport interface VisualScenarioEffectsProps {\n scenario?: VisualScenarioConfig;\n enabled?: boolean;\n applyBackground?: boolean;\n applyFog?: boolean;\n applyRenderer?: boolean;\n applyMaterials?: boolean;\n background?: THREE.ColorRepresentation;\n fogNear?: number;\n fogFar?: number;\n materialFilter?: (input: VisualScenarioMaterialFilterInput) => boolean;\n}\n\nexport interface VisualScenarioMaterialFilterInput {\n object: THREE.Object3D;\n material: THREE.Material;\n}\n\nexport type TrajectoryInput = TrajectoryFrame[] | number[][];\n\nexport interface TrajectoryPlayerProps {\n trajectory: TrajectoryInput;\n fps?: number;\n speed?: number;\n loop?: boolean;\n playing?: boolean;\n mode?: 'kinematic' | 'physics';\n onFrame?: (input: TrajectoryFrameCallbackInput) => void;\n onComplete?: () => void;\n onStateChange?: (input: TrajectoryStateChangeInput) => void;\n}\n\nexport interface TrajectoryFrameCallbackInput {\n frameIndex: number;\n frame: TrajectoryFrame | number[] | undefined;\n}\n\nexport interface TrajectoryStateChangeInput {\n state: PlaybackState;\n}\n\nexport interface SelectionHighlightProps {\n bodyId: number | null;\n color?: string;\n emissiveIntensity?: number;\n}\n\nexport interface ContactListenerProps {\n body: Bodies;\n onContactEnter?: (info: ContactInfo) => void;\n onContactExit?: (info: ContactInfo) => void;\n}\n\nexport interface BodyProps {\n name: Bodies;\n type: 'box' | 'sphere' | 'cylinder';\n size: [number, number, number];\n position?: [number, number, number];\n rgba?: [number, number, number, number];\n mass?: number;\n freejoint?: boolean;\n friction?: string;\n solref?: string;\n solimp?: string;\n condim?: number;\n /** MuJoCo geom group. Group 3 is conventionally used for collision-only helper geoms. */\n group?: number;\n children?: ReactNode;\n}\n\n// ---- Public API (spec: full surface) ----\n\nexport interface MujocoSimAPI {\n // State\n readonly status: 'loading' | 'ready' | 'error';\n readonly config: SceneConfig;\n\n // Simulation control (spec 1.1, 1.2, 1.3)\n reset(): void;\n setSpeed(multiplier: number): void;\n togglePause(): boolean;\n setPaused(paused: boolean): void;\n step(n?: number): void;\n getTime(): number;\n getTimestep(): number;\n applyKeyframe(nameOrIndex: Keyframes | number): void;\n\n // State management (spec 4.1, 4.2, 4.3)\n saveState(): StateSnapshot;\n restoreState(snapshot: StateSnapshot): void;\n setQpos(values: Float64Array | number[]): void;\n setQvel(values: Float64Array | number[]): void;\n getQpos(): Float64Array;\n getQvel(): Float64Array;\n\n // Actuator / control (spec 3.1)\n setCtrl(nameOrValues: Actuators | Record<Actuators, number>, value?: number): void;\n getCtrl(): Float64Array;\n getControlMap(): ControlGroupInfo;\n getActuatedJoints(): ActuatedJointInfo[];\n resolveControlGroup(selector: ControlGroupSelector): ControlGroupInfo | null;\n\n // Force application (spec 8.1)\n applyForce(bodyName: Bodies, force: THREE.Vector3, point?: THREE.Vector3): void;\n applyTorque(bodyName: Bodies, torque: THREE.Vector3): void;\n setExternalForce(bodyName: Bodies, force: THREE.Vector3, torque: THREE.Vector3): void;\n applyGeneralizedForce(values: Float64Array | number[]): void;\n\n // Sensors (spec 2.1)\n getSensorData(name: Sensors): Float64Array | null;\n\n // Contacts (spec 2.4)\n getContacts(): ContactInfo[];\n\n // Model introspection (spec 5.1, 5.2)\n getBodies(): BodyInfo[];\n getJoints(): JointInfo[];\n getGeoms(): GeomInfo[];\n getSites(): SiteInfo[];\n getActuators(): ActuatorInfo[];\n getSensors(): SensorInfo[];\n getCameras(): CameraInfo[];\n\n // Model parameters (spec 5.3)\n getModelOption(): ModelOptions;\n setGravity(g: [number, number, number]): void;\n setTimestep(dt: number): void;\n\n // Raycasting (spec 7.1)\n raycast(origin: THREE.Vector3, direction: THREE.Vector3, maxDist?: number): RayHit | null;\n\n // Keyframes (spec 4.2)\n getKeyframeNames(): string[];\n getKeyframeCount(): number;\n\n // Model loading (spec 9.1)\n loadScene(newConfig: SceneConfig): Promise<void>;\n loadFromFiles(files: FileList | readonly LocalMujocoFile[], options?: LoadFromFilesOptions): Promise<void>;\n addBody(body: SceneObject): Promise<void>;\n removeBody(name: Bodies): Promise<void>;\n recompile(patches?: XmlPatch[]): Promise<void>;\n\n // Canvas\n getCanvas(): HTMLCanvasElement | null;\n getCanvasSnapshot(width?: number, height?: number, mimeType?: string): string;\n captureFrame(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureResult>;\n captureFrameBlob(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureBlobResult>;\n captureCameraFrame(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureResult>;\n captureCameraFrameBlob(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureBlobResult>;\n recordCameraSequence(options: CameraFrameSequenceOptions): Promise<CameraFrameSequenceResult>;\n project2DTo3D(\n x: number,\n y: number,\n cameraPos: THREE.Vector3,\n lookAt: THREE.Vector3\n ): { point: THREE.Vector3; bodyId: number; geomId: number } | null;\n\n // Domain randomization (spec 10.3)\n setBodyMass(name: Bodies, mass: number): void;\n setGeomFriction(name: Geoms, friction: [number, number, number]): void;\n setGeomSize(name: Geoms, size: [number, number, number]): void;\n\n // Internal refs for advanced use\n readonly mjModelRef: React.RefObject<MujocoModel | null>;\n readonly mjDataRef: React.RefObject<MujocoData | null>;\n}\n\nexport type FrameCaptureStatus = 'idle' | 'capturing' | 'captured' | 'error';\n\nexport type FrameCaptureTarget =\n | HTMLCanvasElement\n | HTMLElement\n | null\n | undefined;\n\nexport type FrameCaptureTargetRef =\n React.RefObject<HTMLCanvasElement | HTMLElement | null>;\n\nexport interface FrameCaptureOptions {\n target?: FrameCaptureTarget | FrameCaptureTargetRef;\n type?: string;\n quality?: number;\n waitForAnimationFrame?: boolean;\n}\n\nexport type MujocoFrameCaptureOptions = Omit<FrameCaptureOptions, 'target'>;\n\nexport interface FrameCaptureResult {\n canvas: HTMLCanvasElement;\n dataUrl: string;\n type: string;\n}\n\nexport interface FrameCaptureBlobResult {\n canvas: HTMLCanvasElement;\n blob: Blob;\n type: string;\n}\n\nexport interface FrameCaptureAPI {\n status: FrameCaptureStatus;\n error: Error | null;\n isCapturing: boolean;\n capture: (options?: FrameCaptureOptions) => Promise<FrameCaptureResult>;\n captureBlob: (\n options?: FrameCaptureOptions\n ) => Promise<FrameCaptureBlobResult>;\n reset: () => void;\n}\n\nexport type CameraFrameCaptureVector3 =\n | THREE.Vector3\n | readonly [number, number, number];\n\nexport type CameraFrameCaptureQuaternion =\n | THREE.Quaternion\n | readonly [number, number, number, number];\n\nexport interface CameraFrameCaptureOptions {\n /** Existing Three camera to clone before applying pose overrides. */\n camera?: THREE.Camera;\n /** Named MuJoCo `<camera>` to render from when available in the loaded model. */\n cameraName?: Cameras;\n /** Named MuJoCo site to use as the rendered camera pose. Useful for robot-mounted optical frames. */\n siteName?: Sites;\n /** Named MuJoCo body to use as the rendered camera pose. */\n bodyName?: Bodies;\n position?: CameraFrameCaptureVector3;\n lookAt?: CameraFrameCaptureVector3;\n quaternion?: CameraFrameCaptureQuaternion;\n up?: CameraFrameCaptureVector3;\n width?: number;\n height?: number;\n type?: string;\n quality?: number;\n fov?: number;\n near?: number;\n far?: number;\n /** Provenance for the camera pose used by the capture. Usually set by the MuJoCo provider. */\n source?: CameraFrameCaptureSource;\n}\n\nexport type CameraFrameCaptureSource =\n | { kind: 'mujoco-camera'; cameraName: Cameras }\n | { kind: 'mujoco-site'; siteName: Sites }\n | { kind: 'mujoco-body'; bodyName: Bodies }\n | { kind: 'custom-camera' }\n | { kind: 'explicit-pose' }\n | { kind: 'fallback-camera' };\n\nexport interface CameraFrameCaptureResult {\n canvas: HTMLCanvasElement;\n camera: THREE.Camera;\n dataUrl: string;\n type: string;\n width: number;\n height: number;\n source: CameraFrameCaptureSource;\n}\n\nexport interface CameraFrameCaptureBlobResult {\n canvas: HTMLCanvasElement;\n camera: THREE.Camera;\n blob: Blob;\n type: string;\n width: number;\n height: number;\n source: CameraFrameCaptureSource;\n}\n\nexport interface CameraFrameCaptureAPI {\n status: FrameCaptureStatus;\n error: Error | null;\n isCapturing: boolean;\n capture: (\n options?: CameraFrameCaptureOptions\n ) => Promise<CameraFrameCaptureResult>;\n captureBlob: (\n options?: CameraFrameCaptureOptions\n ) => Promise<CameraFrameCaptureBlobResult>;\n reset: () => void;\n}\n\nexport interface CameraFrameSequenceCamera extends CameraFrameCaptureOptions {\n key: string;\n}\n\nexport interface CameraFrameSequenceFrame {\n frameIndex: number;\n time: number;\n cameras: Record<string, CameraFrameCaptureResult>;\n}\n\nexport interface CameraFrameSequenceCameraSummary {\n key: string;\n width: number;\n height: number;\n source: CameraFrameCaptureSource;\n frameCount: number;\n firstFrameIndex: number | null;\n lastFrameIndex: number | null;\n firstTimestamp: number | null;\n lastTimestamp: number | null;\n}\n\nexport interface CameraFrameSequenceSampleInput extends PhysicsStepInput {\n frameIndex: number;\n time: number;\n}\n\nexport interface CameraFrameSequenceStepInput extends PhysicsStepInput {\n frameIndex: number;\n stepIndex: number;\n time: number;\n}\n\nexport interface CameraFrameSequenceOptions {\n cameras: readonly CameraFrameSequenceCamera[];\n frames: number;\n /** Number of MuJoCo steps between captured frames. Use 0 for static camera provenance captures. */\n stepsPerFrame?: number;\n reset?: boolean;\n captureInitialFrame?: boolean;\n retainFrames?: boolean;\n /**\n * Require each recorded stream to resolve from exactly one mounted MuJoCo\n * camera/site/body selector. Defaults to true because sequence recording is\n * intended for dataset/policy camera streams.\n */\n requireMountedSources?: boolean;\n signal?: AbortSignal;\n /** Called after stepping and before image capture for this frame. Use this to record synchronized state/action rows. */\n onSample?: (input: CameraFrameSequenceSampleInput) => void | Promise<void>;\n /** Called before each MuJoCo step inside sequence recording. Use this to apply policy/control actions. */\n onBeforeStep?: (input: CameraFrameSequenceStepInput) => void | Promise<void>;\n /** Called after each MuJoCo step inside sequence recording. Use this for step-level telemetry. */\n onAfterStep?: (input: CameraFrameSequenceStepInput) => void | Promise<void>;\n onFrame?: (frame: CameraFrameSequenceFrame) => void | Promise<void>;\n}\n\nexport interface CameraFrameSequenceResult {\n frames: CameraFrameSequenceFrame[];\n cameraKeys: string[];\n cameraSummaries: Record<string, CameraFrameSequenceCameraSummary>;\n frameCount: number;\n}\n\nexport interface CameraFrameSequenceRecorderAPI {\n status: FrameCaptureStatus;\n error: Error | null;\n isRecording: boolean;\n record: (options: CameraFrameSequenceOptions) => Promise<CameraFrameSequenceResult>;\n reset: () => void;\n}\n\n// ---- Canvas Props ----\n\nexport type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {\n config: SceneConfig;\n /** R3F content rendered while the MuJoCo WASM module is still loading. */\n loadingFallback?: ReactNode;\n onReady?: (input: ReadyCallbackInput) => void;\n onError?: (error: Error) => void;\n onStep?: (input: StepCallbackInput) => void;\n onSelection?: (input: SelectionCallbackInput) => void;\n // Declarative physics config (spec 1.1)\n gravity?: [number, number, number];\n timestep?: number;\n substeps?: number;\n paused?: boolean;\n speed?: number;\n interpolate?: boolean;\n};\n\n// ---- Hook Return Types ----\n\nexport interface SitePositionResult {\n position: React.RefObject<THREE.Vector3>;\n quaternion: React.RefObject<THREE.Quaternion>;\n}\n\nexport interface MujocoContextValue {\n mujoco: MujocoModule | null;\n status: 'loading' | 'ready' | 'error';\n error: string | null;\n}\n\n/** @deprecated Use `SensorHandle` instead. */\nexport interface SensorResult {\n value: React.RefObject<Float64Array>;\n size: number;\n}\n\nexport interface CtrlHandle {\n /** Read the current ctrl value. */\n read(): number;\n /** Write a ctrl value (goes directly to data.ctrl). */\n write(value: number): void;\n /** Actuator name. */\n name: Actuators;\n /** Actuator control range [min, max]. */\n range: [number, number];\n}\n\nexport interface SensorHandle {\n /** Read the current sensor data. */\n read(): Float64Array;\n /** Sensor dimensionality. */\n dim: number;\n /** Sensor name. */\n name: Sensors;\n}\n\nexport interface BodyStateResult {\n position: React.RefObject<THREE.Vector3>;\n quaternion: React.RefObject<THREE.Quaternion>;\n linearVelocity: React.RefObject<THREE.Vector3>;\n angularVelocity: React.RefObject<THREE.Vector3>;\n}\n\nexport interface JointStateResult {\n position: React.RefObject<number | Float64Array>;\n velocity: React.RefObject<number | Float64Array>;\n}\n","/**\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 { SplatEnvironmentReadinessStatus } from '../types';\nimport type {\n PairedSplatEnvironmentConfig,\n ScenarioMaterialConfig,\n SceneConfig,\n SplatCollisionProxyConfig,\n SplatEnvironmentReadiness,\n SplatEnvironmentMetadata,\n SplatEnvironmentMetadataInput,\n SplatFormat,\n SplatRendererKind,\n SplatSceneConfigInput,\n SplatSceneConfigState,\n SplatSceneInput,\n ScenarioLightingPreset,\n ScenarioLightingProps,\n SplatEnvironmentProps,\n VisualScenarioConfig,\n VisualScenarioExecutionContext,\n VisualScenarioExecutionContextInput,\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 useVisualScenarioExecutionContext({\n scenario,\n environment,\n renderer,\n variantId,\n enabled,\n}: VisualScenarioExecutionContextInput): VisualScenarioExecutionContext {\n return useMemo(\n () =>\n createVisualScenarioExecutionContext({\n scenario,\n environment,\n renderer,\n variantId,\n enabled,\n }),\n [enabled, environment, renderer, scenario, variantId]\n );\n}\n\nexport function createVisualScenarioExecutionContext({\n scenario,\n environment,\n renderer,\n variantId,\n enabled = true,\n}: VisualScenarioExecutionContextInput): VisualScenarioExecutionContext {\n const pairedEnvironment =\n environment ??\n (scenario ? createPairedSplatEnvironment(scenario, { renderer }) : undefined);\n const splat = scenario?.splat;\n const collisionProxy =\n pairedEnvironment?.collisionProxy ?? splat?.collisionProxy ?? undefined;\n const readiness = getSplatEnvironmentReadiness({\n environment: pairedEnvironment,\n scenario,\n renderer,\n enabled,\n });\n const format =\n pairedEnvironment?.splat.format ?? splat?.format ?? readiness.format ?? 'spz';\n\n return {\n scenarioId: scenario?.id ?? pairedEnvironment?.id ?? 'visual-scenario',\n scenarioLabel:\n scenario?.label ?? pairedEnvironment?.label ?? 'Visual scenario',\n variantId,\n seed: scenario?.seed ?? 0,\n lighting: scenario?.lighting ?? 'studio',\n environment: scenario?.environment,\n camera: {\n jitter: scenario?.camera?.jitter ?? 0,\n exposure: scenario?.camera?.exposure ?? 1,\n noise: scenario?.camera?.noise ?? 0,\n blur: scenario?.camera?.blur ?? 0,\n },\n materials: {\n randomizeObjectColors: Boolean(\n scenario?.materials?.randomizeObjectColors\n ),\n randomizeTableMaterial: Boolean(\n scenario?.materials?.randomizeTableMaterial\n ),\n roughness: scenario?.materials?.roughness,\n metalness: scenario?.materials?.metalness,\n },\n splatEnabled: Boolean(splat?.enabled || pairedEnvironment),\n splatSrc: pairedEnvironment?.splat.src ?? splat?.src,\n splatFormat: format,\n splatRenderer: renderer ?? pairedEnvironment?.splat.renderer,\n collisionProxyXmlPath: collisionProxy?.xmlPath,\n collisionProxyStatus: collisionProxy?.status,\n collisionProxyPrimitives: collisionProxy?.primitives ?? [],\n readiness,\n transformSource: 'visualScenario.camera',\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 const readiness = useMemo(\n () =>\n getSplatEnvironmentReadiness({\n environment: scenarioEnvironment,\n scenario,\n renderer,\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n }),\n [\n collisionProxy,\n renderer,\n resolvedCollisionProxy,\n resolvedFormat,\n resolvedSrc,\n scenario,\n scenarioEnvironment,\n ]\n );\n\n return useMemo(\n () => ({\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n readiness,\n userData: createSplatEnvironmentUserData({\n environment: scenarioEnvironment,\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n readiness,\n }),\n }),\n [\n scenarioEnvironment,\n resolvedSrc,\n resolvedFormat,\n resolvedCollisionProxy,\n readiness,\n ]\n );\n}\n\n/**\n * Resolve a visual scenario's paired splat environment and compose its MJCF\n * collision proxy into a MuJoCo scene config.\n *\n * This hook is renderer-agnostic: apps can use it with Spark, another 3DGS\n * renderer, or their own Three scene objects while keeping physics collision\n * files paired with the visual splat metadata.\n */\nexport function useSplatSceneConfig({\n sceneConfig,\n scenario,\n environment,\n enabled = true,\n renderer,\n}: SplatSceneConfigInput): SplatSceneConfigState {\n return useMemo(\n () =>\n createSplatSceneConfig({\n sceneConfig,\n scenario,\n environment,\n enabled,\n renderer,\n }),\n [enabled, environment, renderer, scenario, sceneConfig]\n );\n}\n\n/**\n * Resolve a visual scenario's paired splat environment without requiring React.\n *\n * Use this in codegen, import validators, backend handoff metadata, or app code\n * that needs the same behavior as `useSplatSceneConfig` outside a component.\n */\nexport function createSplatSceneConfig({\n sceneConfig,\n scenario,\n environment,\n enabled = true,\n renderer,\n}: SplatSceneConfigInput): SplatSceneConfigState {\n const resolvedEnvironment = enabled\n ? environment ??\n (scenario\n ? createPairedSplatEnvironment(scenario, { renderer })\n : undefined)\n : undefined;\n const readiness = getSplatEnvironmentReadiness({\n environment: resolvedEnvironment,\n scenario,\n renderer,\n enabled,\n });\n const resolvedSceneConfig = resolvedEnvironment\n ? withSplatEnvironment(sceneConfig, resolvedEnvironment, { renderer })\n : sceneConfig;\n\n return {\n environment: resolvedEnvironment,\n sceneConfig: resolvedSceneConfig,\n enabled:\n enabled && readiness.status !== SplatEnvironmentReadinessStatus.Disabled,\n readiness,\n };\n}\n\nexport function getSplatEnvironmentReadiness({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy,\n enabled = true,\n}: {\n environment?: PairedSplatEnvironmentConfig;\n scenario?: Pick<VisualScenarioConfig, 'splat'>;\n renderer?: SplatRendererKind;\n src?: string;\n format?: SplatFormat;\n collisionProxy?: SplatCollisionProxyConfig;\n enabled?: boolean;\n}): SplatEnvironmentReadiness {\n const splat = scenario?.splat;\n const resolvedSrc = src ?? environment?.splat.src ?? splat?.src;\n const resolvedFormat =\n format ?? environment?.splat.format ?? splat?.format ?? 'spz';\n const resolvedRenderer = renderer ?? environment?.splat.renderer;\n const resolvedCollisionProxy =\n collisionProxy ?? environment?.collisionProxy ?? splat?.collisionProxy ?? undefined;\n const requiresCollisionProxy = splat?.requiresCollisionProxy ?? true;\n\n if (!enabled || (splat && splat.enabled === false && !environment)) {\n return {\n status: SplatEnvironmentReadinessStatus.Disabled,\n ready: false,\n requiresCollisionProxy,\n missing: [],\n format: resolvedFormat,\n renderer: resolvedRenderer,\n message: 'Splat environment is disabled.',\n };\n }\n\n if (!resolvedSrc) {\n return {\n status: SplatEnvironmentReadinessStatus.MissingSplat,\n ready: false,\n requiresCollisionProxy,\n missing: ['splat'],\n format: resolvedFormat,\n renderer: resolvedRenderer,\n message: 'Splat environment is missing a visual asset source.',\n };\n }\n\n if (resolvedRenderer === 'spark' && resolvedFormat !== 'spz') {\n return {\n status: SplatEnvironmentReadinessStatus.UnsupportedFormat,\n ready: false,\n requiresCollisionProxy,\n missing: [],\n format: resolvedFormat,\n renderer: resolvedRenderer,\n message: `Spark splat rendering requires .spz assets; received ${resolvedFormat}.`,\n };\n }\n\n if (requiresCollisionProxy && !resolvedCollisionProxy?.xmlPath) {\n return {\n status: SplatEnvironmentReadinessStatus.MissingCollisionProxy,\n ready: false,\n requiresCollisionProxy,\n missing: ['collisionProxy'],\n format: resolvedFormat,\n renderer: resolvedRenderer,\n message: 'Splat environment is missing paired MJCF collision proxy XML.',\n };\n }\n\n return {\n status: SplatEnvironmentReadinessStatus.Ready,\n ready: true,\n requiresCollisionProxy,\n missing: [],\n format: resolvedFormat,\n renderer: resolvedRenderer,\n message: requiresCollisionProxy\n ? 'Splat environment has visual asset and collision proxy metadata.'\n : 'Splat environment has a visual asset and does not require collision proxy metadata.',\n };\n}\n\n/**\n * Convert a generic visual scenario splat block into a composable splat\n * environment config. Visual-only splats are valid; readiness reports whether\n * a paired MJCF collision proxy is required before training/physics handoff.\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) {\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: collisionProxy?.xmlPath\n ? {\n ...collisionProxy,\n xmlPath: collisionProxy.xmlPath,\n }\n : undefined,\n };\n}\n\nfunction isPairedSplatEnvironment(input: SplatSceneInput): input is PairedSplatEnvironmentConfig {\n return (\n !!input &&\n 'splat' in input &&\n !!input.splat &&\n !('enabled' in input.splat)\n );\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 readiness,\n}: {\n environment?: PairedSplatEnvironmentConfig;\n src?: string;\n format?: SplatFormat;\n collisionProxy?: SplatCollisionProxyConfig;\n readiness?: SplatEnvironmentReadiness;\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 readinessStatus: readiness?.status,\n readinessMessage: readiness?.message,\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"]}
|