mujoco-react 9.4.0 → 9.5.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 +22 -6
- package/dist/{chunk-VDSEPZYQ.js → chunk-6MOK6ZWB.js} +397 -4
- package/dist/chunk-6MOK6ZWB.js.map +1 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +360 -430
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +27 -3
- package/dist/spark.js +156 -3
- package/dist/spark.js.map +1 -1
- package/dist/{types-BuJ4boaq.d.ts → types-BDB9QT6Z.d.ts} +1 -0
- package/package.json +1 -1
- package/src/components/ContactMarkers.tsx +8 -1
- package/src/components/Debug.tsx +154 -3
- package/src/components/DragInteraction.tsx +2 -0
- package/src/components/IkGizmo.tsx +5 -1
- package/src/core/MujocoSimProvider.tsx +5 -5
- package/src/index.ts +1 -0
- package/src/rendering/cameraFrameCapture.ts +259 -28
- package/src/rendering/cameraFrameSource.ts +10 -2
- package/src/spark.tsx +241 -1
- package/src/types.ts +1 -0
- package/dist/chunk-VDSEPZYQ.js.map +0 -1
package/dist/spark.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as _sparkjsdev_spark from '@sparkjsdev/spark';
|
|
3
|
-
import { n as SplatEnvironmentProps, q as PairedSplatEnvironmentConfig, S as SceneConfig, t as SplatEnvironmentReadiness, o as VisualScenarioConfig } from './types-
|
|
3
|
+
import { n as SplatEnvironmentProps, q as PairedSplatEnvironmentConfig, S as SceneConfig, t as SplatEnvironmentReadiness, o as VisualScenarioConfig } from './types-BDB9QT6Z.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
import '@react-three/fiber';
|
|
6
6
|
import 'three';
|
|
@@ -8,6 +8,26 @@ import 'three';
|
|
|
8
8
|
type SparkModule = typeof _sparkjsdev_spark;
|
|
9
9
|
type SparkSplatMeshInstance = InstanceType<SparkModule['SplatMesh']>;
|
|
10
10
|
type SparkSplatStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
11
|
+
interface SparkSplatRenderTuning {
|
|
12
|
+
/** Scale Spark's LoD splat budget for the live viewport. */
|
|
13
|
+
lodSplatScale?: number;
|
|
14
|
+
/** Minimum rendered LoD splat size. Higher values trade detail for speed. */
|
|
15
|
+
lodRenderScale?: number;
|
|
16
|
+
/** Minimum delay between Spark sort passes for the live viewport. */
|
|
17
|
+
minSortIntervalMs?: number;
|
|
18
|
+
}
|
|
19
|
+
interface SparkSplatCaptureTuning extends SparkSplatRenderTuning {
|
|
20
|
+
/** Maximum animation frames to wait for first-capture Spark warm-up. Default: 4. */
|
|
21
|
+
maxWarmupFrames?: number;
|
|
22
|
+
/** Number of pixels sampled when deciding whether a capture is blank. Default: 512. */
|
|
23
|
+
blankSampleCount?: number;
|
|
24
|
+
/** Alpha threshold used by the blank-capture detector. Default: 8. */
|
|
25
|
+
blankAlphaThreshold?: number;
|
|
26
|
+
/** Minimum visible sampled-pixel ratio before retrying capture. Default: 0.02. */
|
|
27
|
+
blankVisibleRatio?: number;
|
|
28
|
+
/** Minimum average sampled RGB sum before retrying capture. Default: 3. */
|
|
29
|
+
blankAverageColor?: number;
|
|
30
|
+
}
|
|
11
31
|
interface SparkSplatLifecycle {
|
|
12
32
|
status: SparkSplatStatus;
|
|
13
33
|
error: Error | null;
|
|
@@ -28,6 +48,10 @@ interface SparkSplatEnvironmentState {
|
|
|
28
48
|
interface SparkSplatEnvironmentProps extends SplatEnvironmentProps {
|
|
29
49
|
/** Enable Spark LoD handling for large splat assets. Default: true. */
|
|
30
50
|
lod?: boolean | 'quality';
|
|
51
|
+
/** Tune Spark's live viewport renderer. Defaults favor interactive FPS. */
|
|
52
|
+
renderTuning?: SparkSplatRenderTuning;
|
|
53
|
+
/** Tune Spark camera-frame capture. Defaults favor sharper snapshots. */
|
|
54
|
+
captureTuning?: SparkSplatCaptureTuning;
|
|
31
55
|
/**
|
|
32
56
|
* Hide meshes whose names include floor, ground, or plane while the splat is
|
|
33
57
|
* active. This mirrors the common hybrid-rendering setup where MJCF keeps
|
|
@@ -70,6 +94,6 @@ declare function useSparkSplatLifecycle({ enabled, initialStatus, onError, onSta
|
|
|
70
94
|
* Import from `mujoco-react/spark` and install `@sparkjsdev/spark` in the app
|
|
71
95
|
* that uses it. The core `mujoco-react` entrypoint does not depend on Spark.
|
|
72
96
|
*/
|
|
73
|
-
declare function SparkSplatEnvironment({ environment, scenario, renderer, src, format, collisionProxy, collisionProxyMetadata, showPlaceholder, children, lod, hideGroundMeshes, onStatusChange, onLoad, onError, ...groupProps }: SparkSplatEnvironmentProps): react_jsx_runtime.JSX.Element;
|
|
97
|
+
declare function SparkSplatEnvironment({ environment, scenario, renderer, src, format, collisionProxy, collisionProxyMetadata, showPlaceholder, children, lod, renderTuning, captureTuning, hideGroundMeshes, onStatusChange, onLoad, onError, ...groupProps }: SparkSplatEnvironmentProps): react_jsx_runtime.JSX.Element;
|
|
74
98
|
|
|
75
|
-
export { SparkSplatEnvironment, type SparkSplatEnvironmentProps, type SparkSplatEnvironmentState, type SparkSplatLifecycle, type SparkSplatStatus, useSparkSplatEnvironment, useSparkSplatLifecycle };
|
|
99
|
+
export { type SparkSplatCaptureTuning, SparkSplatEnvironment, type SparkSplatEnvironmentProps, type SparkSplatEnvironmentState, type SparkSplatLifecycle, type SparkSplatRenderTuning, type SparkSplatStatus, useSparkSplatEnvironment, useSparkSplatLifecycle };
|
package/dist/spark.js
CHANGED
|
@@ -1,10 +1,68 @@
|
|
|
1
|
-
import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment } from './chunk-
|
|
1
|
+
import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-6MOK6ZWB.js';
|
|
2
2
|
import { useThree } from '@react-three/fiber';
|
|
3
3
|
import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
7
|
var sparkDisposeRejectionHandlerRegistered = false;
|
|
8
|
+
function getDefaultLiveRenderTuning(lod) {
|
|
9
|
+
return {
|
|
10
|
+
lodSplatScale: lod === false ? void 0 : lod === "quality" ? 1 : 0.6,
|
|
11
|
+
lodRenderScale: lod === false || lod === "quality" ? void 0 : 1.2,
|
|
12
|
+
minSortIntervalMs: lod === false ? 0 : lod === "quality" ? 32 : 80
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function getDefaultCaptureTuning(lod) {
|
|
16
|
+
return {
|
|
17
|
+
lodSplatScale: lod === false ? void 0 : lod === "quality" ? 1.25 : 1.15,
|
|
18
|
+
lodRenderScale: lod === false ? void 0 : 0.6,
|
|
19
|
+
minSortIntervalMs: 0,
|
|
20
|
+
maxWarmupFrames: 4,
|
|
21
|
+
blankSampleCount: 512,
|
|
22
|
+
blankAlphaThreshold: 8,
|
|
23
|
+
blankVisibleRatio: 0.02,
|
|
24
|
+
blankAverageColor: 3
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function resolveRenderTuning(defaults, tuning) {
|
|
28
|
+
return {
|
|
29
|
+
lodSplatScale: tuning?.lodSplatScale ?? defaults.lodSplatScale,
|
|
30
|
+
lodRenderScale: tuning?.lodRenderScale ?? defaults.lodRenderScale,
|
|
31
|
+
minSortIntervalMs: tuning?.minSortIntervalMs ?? defaults.minSortIntervalMs
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function resolveCaptureTuning(lod, tuning) {
|
|
35
|
+
const defaults = getDefaultCaptureTuning(lod);
|
|
36
|
+
return {
|
|
37
|
+
...resolveRenderTuning(defaults, tuning),
|
|
38
|
+
maxWarmupFrames: tuning?.maxWarmupFrames ?? defaults.maxWarmupFrames,
|
|
39
|
+
blankSampleCount: tuning?.blankSampleCount ?? defaults.blankSampleCount,
|
|
40
|
+
blankAlphaThreshold: tuning?.blankAlphaThreshold ?? defaults.blankAlphaThreshold,
|
|
41
|
+
blankVisibleRatio: tuning?.blankVisibleRatio ?? defaults.blankVisibleRatio,
|
|
42
|
+
blankAverageColor: tuning?.blankAverageColor ?? defaults.blankAverageColor
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function waitForNextAnimationFrame() {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
requestAnimationFrame(() => resolve());
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function isMostlyBlankCapture(pixels, tuning) {
|
|
51
|
+
const sampleCount = Math.max(1, Math.floor(tuning.blankSampleCount));
|
|
52
|
+
const stride = Math.max(4, Math.floor(pixels.length / sampleCount / 4) * 4);
|
|
53
|
+
let sampled = 0;
|
|
54
|
+
let visible = 0;
|
|
55
|
+
let colorTotal = 0;
|
|
56
|
+
for (let i = 0; i < pixels.length; i += stride) {
|
|
57
|
+
const alpha = pixels[i + 3];
|
|
58
|
+
const color = pixels[i] + pixels[i + 1] + pixels[i + 2];
|
|
59
|
+
sampled += 1;
|
|
60
|
+
if (alpha > tuning.blankAlphaThreshold) visible += 1;
|
|
61
|
+
colorTotal += color;
|
|
62
|
+
}
|
|
63
|
+
if (sampled === 0) return true;
|
|
64
|
+
return visible / sampled < tuning.blankVisibleRatio || colorTotal / sampled < tuning.blankAverageColor;
|
|
65
|
+
}
|
|
8
66
|
function useSparkSplatEnvironment({
|
|
9
67
|
sceneConfig,
|
|
10
68
|
scenario,
|
|
@@ -114,6 +172,8 @@ function SparkSplatEnvironment({
|
|
|
114
172
|
showPlaceholder,
|
|
115
173
|
children,
|
|
116
174
|
lod = true,
|
|
175
|
+
renderTuning,
|
|
176
|
+
captureTuning,
|
|
117
177
|
hideGroundMeshes = false,
|
|
118
178
|
onStatusChange,
|
|
119
179
|
onLoad,
|
|
@@ -122,6 +182,7 @@ function SparkSplatEnvironment({
|
|
|
122
182
|
}) {
|
|
123
183
|
const groupRef = useRef(null);
|
|
124
184
|
const sparkRef = useRef(null);
|
|
185
|
+
const captureSparkRef = useRef(null);
|
|
125
186
|
const meshRef = useRef(null);
|
|
126
187
|
const hiddenMeshesRef = useRef([]);
|
|
127
188
|
const onStatusChangeRef = useRef(onStatusChange);
|
|
@@ -137,6 +198,29 @@ function SparkSplatEnvironment({
|
|
|
137
198
|
format,
|
|
138
199
|
collisionProxy: collisionProxyMetadata
|
|
139
200
|
});
|
|
201
|
+
const resolvedRenderTuning = useMemo(
|
|
202
|
+
() => resolveRenderTuning(getDefaultLiveRenderTuning(lod), renderTuning),
|
|
203
|
+
[
|
|
204
|
+
lod,
|
|
205
|
+
renderTuning?.lodRenderScale,
|
|
206
|
+
renderTuning?.lodSplatScale,
|
|
207
|
+
renderTuning?.minSortIntervalMs
|
|
208
|
+
]
|
|
209
|
+
);
|
|
210
|
+
const resolvedCaptureTuning = useMemo(
|
|
211
|
+
() => resolveCaptureTuning(lod, captureTuning),
|
|
212
|
+
[
|
|
213
|
+
captureTuning?.blankAlphaThreshold,
|
|
214
|
+
captureTuning?.blankAverageColor,
|
|
215
|
+
captureTuning?.blankSampleCount,
|
|
216
|
+
captureTuning?.blankVisibleRatio,
|
|
217
|
+
captureTuning?.lodRenderScale,
|
|
218
|
+
captureTuning?.lodSplatScale,
|
|
219
|
+
captureTuning?.maxWarmupFrames,
|
|
220
|
+
captureTuning?.minSortIntervalMs,
|
|
221
|
+
lod
|
|
222
|
+
]
|
|
223
|
+
);
|
|
140
224
|
useEffect(() => {
|
|
141
225
|
onStatusChangeRef.current = onStatusChange;
|
|
142
226
|
}, [onStatusChange]);
|
|
@@ -159,6 +243,10 @@ function SparkSplatEnvironment({
|
|
|
159
243
|
}
|
|
160
244
|
hiddenMeshesRef.current = [];
|
|
161
245
|
}
|
|
246
|
+
function disposeCaptureSpark() {
|
|
247
|
+
safelyDisposeSparkResource(captureSparkRef.current?.renderer);
|
|
248
|
+
captureSparkRef.current = null;
|
|
249
|
+
}
|
|
162
250
|
async function loadSplat() {
|
|
163
251
|
if (!metadata.src) {
|
|
164
252
|
setLifecycleStatus("idle");
|
|
@@ -178,8 +266,60 @@ function SparkSplatEnvironment({
|
|
|
178
266
|
if (disposed || !groupRef.current) return;
|
|
179
267
|
const spark = new sparkModule.SparkRenderer({
|
|
180
268
|
renderer: gl,
|
|
181
|
-
onDirty: invalidate
|
|
269
|
+
onDirty: invalidate,
|
|
270
|
+
...resolvedRenderTuning
|
|
182
271
|
});
|
|
272
|
+
spark.userData[CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY] = async ({
|
|
273
|
+
scene,
|
|
274
|
+
camera,
|
|
275
|
+
width,
|
|
276
|
+
height
|
|
277
|
+
}) => {
|
|
278
|
+
let captureSpark = captureSparkRef.current;
|
|
279
|
+
if (!captureSpark || captureSpark.width !== width || captureSpark.height !== height) {
|
|
280
|
+
disposeCaptureSpark();
|
|
281
|
+
captureSpark = {
|
|
282
|
+
renderer: new sparkModule.SparkRenderer({
|
|
283
|
+
renderer: gl,
|
|
284
|
+
autoUpdate: false,
|
|
285
|
+
lodSplatScale: resolvedCaptureTuning.lodSplatScale,
|
|
286
|
+
lodRenderScale: resolvedCaptureTuning.lodRenderScale,
|
|
287
|
+
minSortIntervalMs: resolvedCaptureTuning.minSortIntervalMs,
|
|
288
|
+
target: {
|
|
289
|
+
width,
|
|
290
|
+
height
|
|
291
|
+
}
|
|
292
|
+
}),
|
|
293
|
+
width,
|
|
294
|
+
height
|
|
295
|
+
};
|
|
296
|
+
captureSparkRef.current = captureSpark;
|
|
297
|
+
}
|
|
298
|
+
const captureRenderer = captureSpark.renderer;
|
|
299
|
+
const maxWarmupFrames = Math.max(
|
|
300
|
+
1,
|
|
301
|
+
Math.floor(resolvedCaptureTuning.maxWarmupFrames)
|
|
302
|
+
);
|
|
303
|
+
let pixels;
|
|
304
|
+
for (let attempt = 0; attempt < maxWarmupFrames; attempt += 1) {
|
|
305
|
+
captureRenderer.renderSize.set(width, height);
|
|
306
|
+
await captureRenderer.update({ scene, camera });
|
|
307
|
+
pixels = await captureRenderer.renderReadTarget({
|
|
308
|
+
scene,
|
|
309
|
+
camera
|
|
310
|
+
});
|
|
311
|
+
if (captureRenderer.activeSplats > 0 && !isMostlyBlankCapture(pixels, resolvedCaptureTuning)) {
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
if (attempt < maxWarmupFrames - 1) {
|
|
315
|
+
await waitForNextAnimationFrame();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (!pixels) {
|
|
319
|
+
throw new Error("Spark camera frame capture did not produce pixels.");
|
|
320
|
+
}
|
|
321
|
+
return { pixels, width, height, flipY: true };
|
|
322
|
+
};
|
|
183
323
|
const mesh = new sparkModule.SplatMesh({
|
|
184
324
|
url: metadata.src,
|
|
185
325
|
lod
|
|
@@ -226,6 +366,7 @@ function SparkSplatEnvironment({
|
|
|
226
366
|
safelyDisposeSparkResource(sparkRef.current);
|
|
227
367
|
sparkRef.current = null;
|
|
228
368
|
}
|
|
369
|
+
disposeCaptureSpark();
|
|
229
370
|
};
|
|
230
371
|
}, [
|
|
231
372
|
gl,
|
|
@@ -233,7 +374,18 @@ function SparkSplatEnvironment({
|
|
|
233
374
|
invalidate,
|
|
234
375
|
lod,
|
|
235
376
|
metadata.format,
|
|
236
|
-
metadata.src
|
|
377
|
+
metadata.src,
|
|
378
|
+
resolvedCaptureTuning.blankAlphaThreshold,
|
|
379
|
+
resolvedCaptureTuning.blankAverageColor,
|
|
380
|
+
resolvedCaptureTuning.blankSampleCount,
|
|
381
|
+
resolvedCaptureTuning.blankVisibleRatio,
|
|
382
|
+
resolvedCaptureTuning.lodRenderScale,
|
|
383
|
+
resolvedCaptureTuning.lodSplatScale,
|
|
384
|
+
resolvedCaptureTuning.maxWarmupFrames,
|
|
385
|
+
resolvedCaptureTuning.minSortIntervalMs,
|
|
386
|
+
resolvedRenderTuning.lodRenderScale,
|
|
387
|
+
resolvedRenderTuning.lodSplatScale,
|
|
388
|
+
resolvedRenderTuning.minSortIntervalMs
|
|
237
389
|
]);
|
|
238
390
|
return /* @__PURE__ */ jsxs(
|
|
239
391
|
SplatEnvironment,
|
|
@@ -255,6 +407,7 @@ function SparkSplatEnvironment({
|
|
|
255
407
|
);
|
|
256
408
|
}
|
|
257
409
|
function safelyDisposeSparkResource(resource) {
|
|
410
|
+
if (!resource) return;
|
|
258
411
|
try {
|
|
259
412
|
silenceSparkWorkerTerminateRejections(resource);
|
|
260
413
|
const result = resource.dispose?.();
|
package/dist/spark.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/spark.tsx"],"names":[],"mappings":";;;;;;AA+CA,IAAI,sCAAA,GAAyC,KAAA;AA2CtC,SAAS,wBAAA,CAAyB;AAAA,EACvC,WAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA,GAAU,IAAA;AAAA,EACV,QAAA,GAAW,OAAA;AAAA,EACX,OAAA;AAAA,EACA;AACF,CAAA,EAQ+B;AAC7B,EAAA,MAAM,aAAa,mBAAA,CAAoB;AAAA,IACrC,WAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAA;AAAA,IACA;AAAA,GACD,CAAA;AACD,EAAA,MAAM,WAAW,mBAAA,CAAoB;AAAA,IACnC,QAAA;AAAA,IACA,aAAa,UAAA,CAAW,WAAA;AAAA,IACxB;AAAA,GACD,CAAA;AACD,EAAA,MAAM,aAAA,GAAgB,OAAA,IAAW,OAAA,CAAQ,QAAA,CAAS,GAAG,CAAA;AACrD,EAAA,MAAM,SAAA,GAAY,OAAA,GAAU,QAAA,CAAS,SAAA,GAAY,UAAA,CAAW,SAAA;AAC5D,EAAA,MAAM,YAAY,sBAAA,CAAuB;AAAA,IACvC,OAAA,EAAS,aAAA;AAAA,IACT,OAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,OAAO,OAAA;AAAA,IACL,OAAO;AAAA,MACL,aAAa,UAAA,CAAW,WAAA;AAAA,MACxB,aAAa,UAAA,CAAW,WAAA;AAAA,MACxB,SAAA;AAAA,MACA,SAAA;AAAA,MACA,KAAA,EAAO;AAAA,QACL,aAAa,UAAA,CAAW,WAAA;AAAA,QACxB,QAAA,EAAU,UAAU,QAAA,GAAW,MAAA;AAAA,QAC/B,GAAA,EAAK,OAAA,GAAU,QAAA,CAAS,GAAA,GAAM,MAAA;AAAA,QAC9B,QAAQ,QAAA,CAAS,MAAA;AAAA,QACjB,GAAG,SAAA,CAAU;AAAA,OACf;AAAA,MACA,OAAA,EAAS;AAAA,KACX,CAAA;AAAA,IACA,CAAC,OAAA,EAAS,SAAA,EAAW,UAAU,SAAA,EAAW,aAAA,EAAe,UAAU,UAAU;AAAA,GAC/E;AACF;AAQO,SAAS,sBAAA,CAAuB;AAAA,EACrC,OAAA,GAAU,IAAA;AAAA,EACV,aAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,GAKI,EAAC,EAAwB;AAC3B,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,QAAA;AAAA,IAC1B,aAAA,KAAkB,UAAU,SAAA,GAAY,MAAA;AAAA,GAC1C;AACA,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,SAAA,CAAU,OAAA,GAAU,aAAA,IAAiB,SAAA,GAAY,MAAM,CAAA;AACvD,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,CAAC,OAAA,EAAS,aAAa,CAAC,CAAA;AAE3B,EAAA,MAAM,kBAAA,GAAqB,WAAA;AAAA,IACzB,CAAC,UAAA,KAAiC;AAChC,MAAA,SAAA,CAAU,UAAU,CAAA;AACpB,MAAA,IAAI,eAAe,OAAA,EAAS;AAC1B,QAAA,QAAA,CAAS,IAAI,CAAA;AAAA,MACf;AACA,MAAA,cAAA,GAAiB,UAAU,CAAA;AAAA,IAC7B,CAAA;AAAA,IACA,CAAC,cAAc;AAAA,GACjB;AAEA,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,CAAC,SAAA,KAAqB;AACpB,MAAA,QAAA,CAAS,SAAS,CAAA;AAClB,MAAA,SAAA,CAAU,OAAO,CAAA;AACjB,MAAA,OAAA,GAAU,SAAS,CAAA;AAAA,IACrB,CAAA;AAAA,IACA,CAAC,OAAO;AAAA,GACV;AAEA,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC9B,IAAA,SAAA,CAAU,OAAA,GAAU,aAAA,IAAiB,SAAA,GAAY,MAAM,CAAA;AACvD,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,CAAC,OAAA,EAAS,aAAa,CAAC,CAAA;AAE3B,EAAA,OAAO,OAAA;AAAA,IACL,OAAO;AAAA,MACL,MAAA;AAAA,MACA,KAAA;AAAA,MACA,WAAW,MAAA,KAAW,SAAA;AAAA,MACtB,SAAS,MAAA,KAAW,OAAA;AAAA,MACpB,SAAS,MAAA,KAAW,OAAA;AAAA,MACpB,KAAA,EAAO;AAAA,QACL,cAAA,EAAgB,kBAAA;AAAA,QAChB,OAAA,EAAS;AAAA,OACX;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,WAAA,EAAa,kBAAA,EAAoB,OAAO,MAAM;AAAA,GACxD;AACF;AAQO,SAAS,qBAAA,CAAsB;AAAA,EACpC,WAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA,GAAW,OAAA;AAAA,EACX,GAAA;AAAA,EACA,MAAA;AAAA,EACA,cAAA;AAAA,EACA,sBAAA;AAAA,EACA,eAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAA,GAAM,IAAA;AAAA,EACN,gBAAA,GAAmB,KAAA;AAAA,EACnB,cAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,GAAG;AACL,CAAA,EAA+B;AAC7B,EAAA,MAAM,QAAA,GAAW,OAAoB,IAAI,CAAA;AACzC,EAAA,MAAM,QAAA,GAAW,OAAqC,IAAI,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,OAAsC,IAAI,CAAA;AAC1D,EAAA,MAAM,eAAA,GAAkB,MAAA,CAAqB,EAAE,CAAA;AAC/C,EAAA,MAAM,iBAAA,GAAoB,OAAO,cAAc,CAAA;AAC/C,EAAA,MAAM,SAAA,GAAY,OAAO,MAAM,CAAA;AAC/B,EAAA,MAAM,UAAA,GAAa,OAAO,OAAO,CAAA;AACjC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAA2B,MAAM,CAAA;AAC7D,EAAA,MAAM,EAAE,EAAA,EAAI,UAAA,EAAW,GAAI,QAAA,EAAS;AACpC,EAAA,MAAM,WAAW,mBAAA,CAAoB;AAAA,IACnC,WAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,GAAA;AAAA,IACA,MAAA;AAAA,IACA,cAAA,EAAgB;AAAA,GACjB,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,iBAAA,CAAkB,OAAA,GAAU,cAAA;AAAA,EAC9B,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAEnB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,SAAA,CAAU,OAAA,GAAU,MAAA;AAAA,EACtB,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAAA,EACvB,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,QAAA,GAAW,KAAA;AACf,IAAA,kCAAA,EAAmC;AAEnC,IAAA,SAAS,mBAAmB,UAAA,EAA8B;AACxD,MAAA,SAAA,CAAU,UAAU,CAAA;AACpB,MAAA,iBAAA,CAAkB,UAAU,UAAU,CAAA;AAAA,IACxC;AAEA,IAAA,SAAS,mBAAA,GAAsB;AAC7B,MAAA,KAAA,MAAW,IAAA,IAAQ,gBAAgB,OAAA,EAAS;AAC1C,QAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,MACjB;AACA,MAAA,eAAA,CAAgB,UAAU,EAAC;AAAA,IAC7B;AAEA,IAAA,eAAe,SAAA,GAAY;AACzB,MAAA,IAAI,CAAC,SAAS,GAAA,EAAK;AACjB,QAAA,kBAAA,CAAmB,MAAM,CAAA;AACzB,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,QAAA,CAAS,WAAW,KAAA,EAAO;AAC7B,QAAA,MAAM,yBAAyB,IAAI,KAAA;AAAA,UACjC,CAAA,2DAAA,EAA8D,SAAS,MAAM,CAAA,EAAA;AAAA,SAC/E;AACA,QAAA,kBAAA,CAAmB,OAAO,CAAA;AAC1B,QAAA,UAAA,CAAW,UAAU,sBAAsB,CAAA;AAC3C,QAAA;AAAA,MACF;AAEA,MAAA,kBAAA,CAAmB,SAAS,CAAA;AAE5B,MAAA,IAAI;AACF,QAAA,MAAM,WAAA,GAAc,MAAM,OAAO,mBAAmB,CAAA;AACpD,QAAA,IAAI,QAAA,IAAY,CAAC,QAAA,CAAS,OAAA,EAAS;AAEnC,QAAA,MAAM,KAAA,GAAQ,IAAI,WAAA,CAAY,aAAA,CAAc;AAAA,UAC1C,QAAA,EAAU,EAAA;AAAA,UACV,OAAA,EAAS;AAAA,SACV,CAAA;AACD,QAAA,MAAM,IAAA,GAAO,IAAI,WAAA,CAAY,SAAA,CAAU;AAAA,UACrC,KAAK,QAAA,CAAS,GAAA;AAAA,UACd;AAAA,SACD,CAAA;AACD,QAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AAEZ,QAAA,QAAA,CAAS,OAAA,CAAQ,IAAI,KAAK,CAAA;AAC1B,QAAA,QAAA,CAAS,OAAA,CAAQ,IAAI,IAAI,CAAA;AACzB,QAAA,QAAA,CAAS,OAAA,GAAU,KAAA;AACnB,QAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAElB,QAAA,IAAI,gBAAA,IAAoB,QAAA,CAAS,OAAA,CAAQ,MAAA,EAAQ;AAC/C,UAAA,QAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,QAAA,CAAS,CAAC,MAAA,KAAW;AAC3C,YAAA,IACE,EAAE,MAAA,YAAwB,KAAA,CAAA,IAAA,CAAA,IAC1B,MAAA,KAAY,IAAA,EACZ;AACA,cAAA;AAAA,YACF;AACA,YAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,WAAA,EAAY;AACrC,YAAA,IACE,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,IACrB,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,IACtB,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,EACrB;AACA,cAAA,MAAA,CAAO,OAAA,GAAU,KAAA;AACjB,cAAA,eAAA,CAAgB,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,YACrC;AAAA,UACF,CAAC,CAAA;AAAA,QACH;AAEA,QAAA,MAAM,IAAA,CAAK,WAAA;AACX,QAAA,IAAI,QAAA,EAAU;AACd,QAAA,kBAAA,CAAmB,OAAO,CAAA;AAC1B,QAAA,SAAA,CAAU,UAAU,IAAI,CAAA;AACxB,QAAA,UAAA,EAAW;AAAA,MACb,SAAS,KAAA,EAAO;AACd,QAAA,MAAM,eAAA,GACJ,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAC1D,QAAA,kBAAA,CAAmB,OAAO,CAAA;AAC1B,QAAA,UAAA,CAAW,UAAU,eAAe,CAAA;AAAA,MACtC;AAAA,IACF;AAEA,IAAA,KAAK,SAAA,EAAU;AAEf,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,GAAW,IAAA;AACX,MAAA,mBAAA,EAAoB;AAEpB,MAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,QAAA,QAAA,CAAS,OAAA,EAAS,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA;AACxC,QAAA,0BAAA,CAA2B,QAAQ,OAAO,CAAA;AAC1C,QAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAAA,MACpB;AAEA,MAAA,IAAI,SAAS,OAAA,EAAS;AACpB,QAAA,QAAA,CAAS,OAAA,EAAS,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA;AACzC,QAAA,0BAAA,CAA2B,SAAS,OAAO,CAAA;AAC3C,QAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AAAA,MACrB;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG;AAAA,IACD,EAAA;AAAA,IACA,gBAAA;AAAA,IACA,UAAA;AAAA,IACA,GAAA;AAAA,IACA,QAAA,CAAS,MAAA;AAAA,IACT,QAAA,CAAS;AAAA,GACV,CAAA;AAED,EAAA,uBACE,IAAA;AAAA,IAAC,gBAAA;AAAA,IAAA;AAAA,MACE,GAAG,UAAA;AAAA,MACJ,WAAA;AAAA,MACA,QAAA;AAAA,MACA,QAAA;AAAA,MACA,KAAK,QAAA,CAAS,GAAA;AAAA,MACd,QAAQ,QAAA,CAAS,MAAA;AAAA,MACjB,wBAAwB,QAAA,CAAS,cAAA;AAAA,MACjC,cAAA;AAAA,MACA,eAAA,EAAiB,mBAAmB,MAAA,KAAW,OAAA;AAAA,MAE/C,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,OAAA,EAAA,EAAM,KAAK,QAAA,EAAU,CAAA;AAAA,QACrB;AAAA;AAAA;AAAA,GACH;AAEJ;AAEA,SAAS,2BAA2B,QAAA,EAA2B;AAC7D,EAAA,IAAI;AACF,IAAA,qCAAA,CAAsC,QAAQ,CAAA;AAC9C,IAAA,MAAM,MAAA,GAAS,SAAS,OAAA,IAAU;AAClC,IAAA,IAAI,aAAA,CAAc,MAAM,CAAA,EAAG;AACzB,MAAA,KAAK,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA,CAAE,MAAM,uBAAuB,CAAA;AAAA,IAC5D;AAAA,EACF,SAAS,KAAA,EAAO;AACd,IAAA,uBAAA,CAAwB,KAAK,CAAA;AAAA,EAC/B;AACF;AAEA,SAAS,cAAc,KAAA,EAA+C;AACpE,EAAA,OACE,OAAO,UAAU,QAAA,IACjB,KAAA,KAAU,QACV,MAAA,IAAU,KAAA,IACV,OAAQ,KAAA,CAA6B,IAAA,KAAS,UAAA;AAElD;AAEA,SAAS,sCAAsC,QAAA,EAA2B;AACxE,EAAA,MAAM,OAAA,GAAU,gBAAgB,QAAQ,CAAA;AACxC,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,CAAC,OAAO,QAAA,EAAU;AAEtB,IAAA,KAAA,MAAW,OAAA,IAAW,MAAA,CAAO,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAA,EAAG;AACpD,MAAA,MAAM,SAAS,OAAA,CAAQ,MAAA;AACvB,MAAA,IAAI,CAAC,MAAA,EAAQ;AAEb,MAAA,OAAA,CAAQ,MAAA,GAAS,CAAC,KAAA,KAAmB;AACnC,QAAA,IAAI,CAAC,2BAAA,CAA4B,KAAK,CAAA,EAAG;AACvC,UAAA,MAAA,CAAO,KAAK,CAAA;AAAA,QACd;AAAA,MACF,CAAA;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,QAAA,EAA8C;AACrE,EAAA,MAAM,aAAA,GAAgB,QAAA;AACtB,EAAA,OAAO;AAAA,IACL,aAAA,CAAc,MAAA;AAAA,IACd,aAAA,CAAc,UAAA;AAAA,IACd,aAAA,CAAc;AAAA,IACd,MAAA,CAAO,CAAC,MAAA,KAAsC,OAAA,CAAQ,MAAM,CAAC,CAAA;AACjE;AAEA,SAAS,wBAAwB,KAAA,EAAgB;AAC/C,EAAA,IACE,KAAA,YAAiB,SACjB,KAAA,CAAM,OAAA,CAAQ,aAAY,CAAE,QAAA,CAAS,kBAAkB,CAAA,EACvD;AACA,IAAA;AAAA,EACF;AAEA,EAAA,OAAA,CAAQ,IAAA,CAAK,kDAAkD,KAAK,CAAA;AACtE;AAEA,SAAS,kCAAA,GAAqC;AAC5C,EAAA,IACE,0CACA,OAAO,MAAA,KAAW,eAClB,OAAO,MAAA,CAAO,qBAAqB,UAAA,EACnC;AACA,IAAA;AAAA,EACF;AAEA,EAAA,sCAAA,GAAyC,IAAA;AACzC,EAAA,MAAA,CAAO,gBAAA,CAAiB,oBAAA,EAAsB,CAAC,KAAA,KAAU;AACvD,IAAA,IAAI,2BAAA,CAA4B,KAAA,CAAM,MAAM,CAAA,EAAG;AAC7C,MAAA,KAAA,CAAM,cAAA,EAAe;AAAA,IACvB;AAAA,EACF,CAAC,CAAA;AACH;AAEA,SAAS,4BAA4B,MAAA,EAAiB;AACpD,EAAA,OACE,kBAAkB,KAAA,IAClB,MAAA,CAAO,QAAQ,WAAA,EAAY,CAAE,SAAS,kBAAkB,CAAA;AAE5D","file":"spark.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { useThree } from '@react-three/fiber';\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport * as THREE from 'three';\nimport {\n SplatEnvironment,\n useSplatEnvironment,\n useSplatSceneConfig,\n} from './components/VisualScenario';\nimport type {\n PairedSplatEnvironmentConfig,\n SceneConfig,\n SplatEnvironmentProps,\n SplatEnvironmentReadiness,\n VisualScenarioConfig,\n} from './types';\n\ntype SparkModule = typeof import('@sparkjsdev/spark');\ntype SparkRendererInstance = InstanceType<SparkModule['SparkRenderer']>;\ntype SparkSplatMeshInstance = InstanceType<SparkModule['SplatMesh']>;\ntype SparkDisposable = {\n dispose?: () => unknown;\n};\ntype SparkWorkerMessage = {\n reject?: (error: unknown) => void;\n};\ntype SparkWorkerLike = {\n messages?: Record<string, SparkWorkerMessage>;\n};\ntype SparkResourceWithWorkers = SparkDisposable & {\n worker?: SparkWorkerLike;\n sortWorker?: SparkWorkerLike;\n lodWorker?: SparkWorkerLike;\n};\n\nexport type SparkSplatStatus = 'idle' | 'loading' | 'ready' | 'error';\n\nlet sparkDisposeRejectionHandlerRegistered = false;\n\nexport interface SparkSplatLifecycle {\n status: SparkSplatStatus;\n error: Error | null;\n isLoading: boolean;\n isReady: boolean;\n isError: boolean;\n props: Pick<SparkSplatEnvironmentProps, 'onStatusChange' | 'onError'>;\n reset: () => void;\n}\n\nexport interface SparkSplatEnvironmentState {\n environment: PairedSplatEnvironmentConfig | undefined;\n sceneConfig: SceneConfig;\n readiness: SplatEnvironmentReadiness;\n lifecycle: SparkSplatLifecycle;\n props: Pick<\n SparkSplatEnvironmentProps,\n 'environment' | 'scenario' | 'src' | 'format' | 'onStatusChange' | 'onError'\n >;\n enabled: boolean;\n}\n\nexport interface SparkSplatEnvironmentProps extends SplatEnvironmentProps {\n /** Enable Spark LoD handling for large splat assets. Default: true. */\n lod?: boolean | 'quality';\n /**\n * Hide meshes whose names include floor, ground, or plane while the splat is\n * active. This mirrors the common hybrid-rendering setup where MJCF keeps\n * collision geometry but the splat owns the visual environment.\n */\n hideGroundMeshes?: boolean;\n onStatusChange?: (status: SparkSplatStatus) => void;\n onLoad?: (mesh: SparkSplatMeshInstance) => void;\n onError?: (error: Error) => void;\n}\n\n/**\n * Resolve a visual scenario's paired splat environment, compose its MJCF\n * collision proxy into the MuJoCo scene config, and expose Spark lifecycle\n * props for `<SparkSplatEnvironment />`.\n */\nexport function useSparkSplatEnvironment({\n sceneConfig,\n scenario,\n environment,\n enabled = true,\n renderer = 'spark',\n onError,\n onStatusChange,\n}: {\n sceneConfig: SceneConfig;\n scenario?: VisualScenarioConfig;\n environment?: PairedSplatEnvironmentConfig;\n enabled?: boolean;\n renderer?: 'spark';\n onError?: (error: Error) => void;\n onStatusChange?: (status: SparkSplatStatus) => void;\n}): SparkSplatEnvironmentState {\n const splatScene = useSplatSceneConfig({\n sceneConfig,\n scenario,\n environment,\n enabled,\n renderer,\n });\n const metadata = useSplatEnvironment({\n scenario,\n environment: splatScene.environment,\n renderer,\n });\n const renderEnabled = enabled && Boolean(metadata.src);\n const readiness = enabled ? metadata.readiness : splatScene.readiness;\n const lifecycle = useSparkSplatLifecycle({\n enabled: renderEnabled,\n onError,\n onStatusChange,\n });\n\n return useMemo(\n () => ({\n environment: splatScene.environment,\n sceneConfig: splatScene.sceneConfig,\n readiness,\n lifecycle,\n props: {\n environment: splatScene.environment,\n scenario: enabled ? scenario : undefined,\n src: enabled ? metadata.src : undefined,\n format: metadata.format,\n ...lifecycle.props,\n },\n enabled: renderEnabled,\n }),\n [enabled, lifecycle, metadata, readiness, renderEnabled, scenario, splatScene]\n );\n}\n\n/**\n * Tracks Spark 3DGS loading state for UI that wraps `SparkSplatEnvironment`.\n *\n * Use the returned `props` with `<SparkSplatEnvironment {...lifecycle.props} />`\n * to avoid repeating status/error state in app code.\n */\nexport function useSparkSplatLifecycle({\n enabled = true,\n initialStatus,\n onError,\n onStatusChange,\n}: {\n enabled?: boolean;\n initialStatus?: SparkSplatStatus;\n onError?: (error: Error) => void;\n onStatusChange?: (status: SparkSplatStatus) => void;\n} = {}): SparkSplatLifecycle {\n const [status, setStatus] = useState<SparkSplatStatus>(\n initialStatus ?? (enabled ? 'loading' : 'idle')\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n setStatus(enabled ? initialStatus ?? 'loading' : 'idle');\n setError(null);\n }, [enabled, initialStatus]);\n\n const handleStatusChange = useCallback(\n (nextStatus: SparkSplatStatus) => {\n setStatus(nextStatus);\n if (nextStatus !== 'error') {\n setError(null);\n }\n onStatusChange?.(nextStatus);\n },\n [onStatusChange]\n );\n\n const handleError = useCallback(\n (nextError: Error) => {\n setError(nextError);\n setStatus('error');\n onError?.(nextError);\n },\n [onError]\n );\n\n const reset = useCallback(() => {\n setStatus(enabled ? initialStatus ?? 'loading' : 'idle');\n setError(null);\n }, [enabled, initialStatus]);\n\n return useMemo(\n () => ({\n status,\n error,\n isLoading: status === 'loading',\n isReady: status === 'ready',\n isError: status === 'error',\n props: {\n onStatusChange: handleStatusChange,\n onError: handleError,\n },\n reset,\n }),\n [error, handleError, handleStatusChange, reset, status]\n );\n}\n\n/**\n * Optional SparkJS-backed Gaussian splat renderer for React Three Fiber scenes.\n *\n * Import from `mujoco-react/spark` and install `@sparkjsdev/spark` in the app\n * that uses it. The core `mujoco-react` entrypoint does not depend on Spark.\n */\nexport function SparkSplatEnvironment({\n environment,\n scenario,\n renderer = 'spark',\n src,\n format,\n collisionProxy,\n collisionProxyMetadata,\n showPlaceholder,\n children,\n lod = true,\n hideGroundMeshes = false,\n onStatusChange,\n onLoad,\n onError,\n ...groupProps\n}: SparkSplatEnvironmentProps) {\n const groupRef = useRef<THREE.Group>(null);\n const sparkRef = useRef<SparkRendererInstance | null>(null);\n const meshRef = useRef<SparkSplatMeshInstance | null>(null);\n const hiddenMeshesRef = useRef<THREE.Mesh[]>([]);\n const onStatusChangeRef = useRef(onStatusChange);\n const onLoadRef = useRef(onLoad);\n const onErrorRef = useRef(onError);\n const [status, setStatus] = useState<SparkSplatStatus>('idle');\n const { gl, invalidate } = useThree();\n const metadata = useSplatEnvironment({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy: collisionProxyMetadata,\n });\n\n useEffect(() => {\n onStatusChangeRef.current = onStatusChange;\n }, [onStatusChange]);\n\n useEffect(() => {\n onLoadRef.current = onLoad;\n }, [onLoad]);\n\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n useEffect(() => {\n let disposed = false;\n ensureSparkDisposeRejectionHandler();\n\n function setLifecycleStatus(nextStatus: SparkSplatStatus) {\n setStatus(nextStatus);\n onStatusChangeRef.current?.(nextStatus);\n }\n\n function restoreHiddenMeshes() {\n for (const mesh of hiddenMeshesRef.current) {\n mesh.visible = true;\n }\n hiddenMeshesRef.current = [];\n }\n\n async function loadSplat() {\n if (!metadata.src) {\n setLifecycleStatus('idle');\n return;\n }\n\n if (metadata.format !== 'spz') {\n const unsupportedFormatError = new Error(\n `SparkSplatEnvironment only supports .spz assets; received \"${metadata.format}\".`\n );\n setLifecycleStatus('error');\n onErrorRef.current?.(unsupportedFormatError);\n return;\n }\n\n setLifecycleStatus('loading');\n\n try {\n const sparkModule = await import('@sparkjsdev/spark');\n if (disposed || !groupRef.current) return;\n\n const spark = new sparkModule.SparkRenderer({\n renderer: gl,\n onDirty: invalidate,\n });\n const mesh = new sparkModule.SplatMesh({\n url: metadata.src,\n lod,\n });\n mesh.name = 'GaussianSplatMesh';\n\n groupRef.current.add(spark);\n groupRef.current.add(mesh);\n sparkRef.current = spark;\n meshRef.current = mesh;\n\n if (hideGroundMeshes && groupRef.current.parent) {\n groupRef.current.parent.traverse((object) => {\n if (\n !(object instanceof THREE.Mesh) ||\n object === (mesh as unknown as THREE.Object3D)\n ) {\n return;\n }\n const name = object.name.toLowerCase();\n if (\n name.includes('floor') ||\n name.includes('ground') ||\n name.includes('plane')\n ) {\n object.visible = false;\n hiddenMeshesRef.current.push(object);\n }\n });\n }\n\n await mesh.initialized;\n if (disposed) return;\n setLifecycleStatus('ready');\n onLoadRef.current?.(mesh);\n invalidate();\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error));\n setLifecycleStatus('error');\n onErrorRef.current?.(normalizedError);\n }\n }\n\n void loadSplat();\n\n return () => {\n disposed = true;\n restoreHiddenMeshes();\n\n if (meshRef.current) {\n groupRef.current?.remove(meshRef.current);\n safelyDisposeSparkResource(meshRef.current);\n meshRef.current = null;\n }\n\n if (sparkRef.current) {\n groupRef.current?.remove(sparkRef.current);\n safelyDisposeSparkResource(sparkRef.current);\n sparkRef.current = null;\n }\n };\n }, [\n gl,\n hideGroundMeshes,\n invalidate,\n lod,\n metadata.format,\n metadata.src,\n ]);\n\n return (\n <SplatEnvironment\n {...groupProps}\n environment={environment}\n scenario={scenario}\n renderer={renderer}\n src={metadata.src}\n format={metadata.format}\n collisionProxyMetadata={metadata.collisionProxy}\n collisionProxy={collisionProxy}\n showPlaceholder={showPlaceholder ?? status !== 'ready'}\n >\n <group ref={groupRef} />\n {children}\n </SplatEnvironment>\n );\n}\n\nfunction safelyDisposeSparkResource(resource: SparkDisposable) {\n try {\n silenceSparkWorkerTerminateRejections(resource);\n const result = resource.dispose?.();\n if (isPromiseLike(result)) {\n void Promise.resolve(result).catch(handleSparkDisposeError);\n }\n } catch (error) {\n handleSparkDisposeError(error);\n }\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof (value as { then?: unknown }).then === 'function'\n );\n}\n\nfunction silenceSparkWorkerTerminateRejections(resource: SparkDisposable) {\n const workers = getSparkWorkers(resource);\n for (const worker of workers) {\n if (!worker.messages) continue;\n\n for (const message of Object.values(worker.messages)) {\n const reject = message.reject;\n if (!reject) continue;\n\n message.reject = (error: unknown) => {\n if (!isSparkWorkerTerminateError(error)) {\n reject(error);\n }\n };\n }\n }\n}\n\nfunction getSparkWorkers(resource: SparkDisposable): SparkWorkerLike[] {\n const sparkResource = resource as SparkResourceWithWorkers;\n return [\n sparkResource.worker,\n sparkResource.sortWorker,\n sparkResource.lodWorker,\n ].filter((worker): worker is SparkWorkerLike => Boolean(worker));\n}\n\nfunction handleSparkDisposeError(error: unknown) {\n if (\n error instanceof Error &&\n error.message.toLowerCase().includes('worker terminate')\n ) {\n return;\n }\n\n console.warn('[mujoco-react] Spark resource disposal failed.', error);\n}\n\nfunction ensureSparkDisposeRejectionHandler() {\n if (\n sparkDisposeRejectionHandlerRegistered ||\n typeof window === 'undefined' ||\n typeof window.addEventListener !== 'function'\n ) {\n return;\n }\n\n sparkDisposeRejectionHandlerRegistered = true;\n window.addEventListener('unhandledrejection', (event) => {\n if (isSparkWorkerTerminateError(event.reason)) {\n event.preventDefault();\n }\n });\n}\n\nfunction isSparkWorkerTerminateError(reason: unknown) {\n return (\n reason instanceof Error &&\n reason.message.toLowerCase().includes('worker terminate')\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/spark.tsx"],"names":[],"mappings":"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","file":"spark.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { useThree } from '@react-three/fiber';\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport * as THREE from 'three';\nimport {\n SplatEnvironment,\n useSplatEnvironment,\n useSplatSceneConfig,\n} from './components/VisualScenario';\nimport {\n CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY,\n} from './rendering/cameraFrameCapture';\nimport type {\n CameraFrameCaptureRenderInput,\n CameraFrameCaptureRenderResult,\n} from './rendering/cameraFrameCapture';\nimport type {\n PairedSplatEnvironmentConfig,\n SceneConfig,\n SplatEnvironmentProps,\n SplatEnvironmentReadiness,\n VisualScenarioConfig,\n} from './types';\n\ntype SparkModule = typeof import('@sparkjsdev/spark');\ntype SparkRendererInstance = InstanceType<SparkModule['SparkRenderer']>;\ntype SparkSplatMeshInstance = InstanceType<SparkModule['SplatMesh']>;\ntype SparkDisposable = {\n dispose?: () => unknown;\n};\ntype SparkCaptureRenderer = {\n renderer: SparkRendererInstance;\n width: number;\n height: number;\n};\ntype SparkWorkerMessage = {\n reject?: (error: unknown) => void;\n};\ntype SparkWorkerLike = {\n messages?: Record<string, SparkWorkerMessage>;\n};\ntype SparkResourceWithWorkers = SparkDisposable & {\n worker?: SparkWorkerLike;\n sortWorker?: SparkWorkerLike;\n lodWorker?: SparkWorkerLike;\n};\n\nexport type SparkSplatStatus = 'idle' | 'loading' | 'ready' | 'error';\n\nlet sparkDisposeRejectionHandlerRegistered = false;\n\nexport interface SparkSplatRenderTuning {\n /** Scale Spark's LoD splat budget for the live viewport. */\n lodSplatScale?: number;\n /** Minimum rendered LoD splat size. Higher values trade detail for speed. */\n lodRenderScale?: number;\n /** Minimum delay between Spark sort passes for the live viewport. */\n minSortIntervalMs?: number;\n}\n\nexport interface SparkSplatCaptureTuning extends SparkSplatRenderTuning {\n /** Maximum animation frames to wait for first-capture Spark warm-up. Default: 4. */\n maxWarmupFrames?: number;\n /** Number of pixels sampled when deciding whether a capture is blank. Default: 512. */\n blankSampleCount?: number;\n /** Alpha threshold used by the blank-capture detector. Default: 8. */\n blankAlphaThreshold?: number;\n /** Minimum visible sampled-pixel ratio before retrying capture. Default: 0.02. */\n blankVisibleRatio?: number;\n /** Minimum average sampled RGB sum before retrying capture. Default: 3. */\n blankAverageColor?: number;\n}\n\ntype ResolvedSparkSplatCaptureTuning = Required<\n Pick<\n SparkSplatCaptureTuning,\n | 'maxWarmupFrames'\n | 'blankSampleCount'\n | 'blankAlphaThreshold'\n | 'blankVisibleRatio'\n | 'blankAverageColor'\n >\n> &\n SparkSplatRenderTuning;\n\nfunction getDefaultLiveRenderTuning(\n lod: SparkSplatEnvironmentProps['lod']\n): SparkSplatRenderTuning {\n return {\n lodSplatScale: lod === false ? undefined : lod === 'quality' ? 1 : 0.6,\n lodRenderScale: lod === false || lod === 'quality' ? undefined : 1.2,\n minSortIntervalMs: lod === false ? 0 : lod === 'quality' ? 32 : 80,\n };\n}\n\nfunction getDefaultCaptureTuning(\n lod: SparkSplatEnvironmentProps['lod']\n): ResolvedSparkSplatCaptureTuning {\n return {\n lodSplatScale: lod === false ? undefined : lod === 'quality' ? 1.25 : 1.15,\n lodRenderScale: lod === false ? undefined : 0.6,\n minSortIntervalMs: 0,\n maxWarmupFrames: 4,\n blankSampleCount: 512,\n blankAlphaThreshold: 8,\n blankVisibleRatio: 0.02,\n blankAverageColor: 3,\n };\n}\n\nfunction resolveRenderTuning(\n defaults: SparkSplatRenderTuning,\n tuning: SparkSplatRenderTuning | undefined\n): SparkSplatRenderTuning {\n return {\n lodSplatScale: tuning?.lodSplatScale ?? defaults.lodSplatScale,\n lodRenderScale: tuning?.lodRenderScale ?? defaults.lodRenderScale,\n minSortIntervalMs: tuning?.minSortIntervalMs ?? defaults.minSortIntervalMs,\n };\n}\n\nfunction resolveCaptureTuning(\n lod: SparkSplatEnvironmentProps['lod'],\n tuning: SparkSplatCaptureTuning | undefined\n): ResolvedSparkSplatCaptureTuning {\n const defaults = getDefaultCaptureTuning(lod);\n return {\n ...resolveRenderTuning(defaults, tuning),\n maxWarmupFrames: tuning?.maxWarmupFrames ?? defaults.maxWarmupFrames,\n blankSampleCount: tuning?.blankSampleCount ?? defaults.blankSampleCount,\n blankAlphaThreshold:\n tuning?.blankAlphaThreshold ?? defaults.blankAlphaThreshold,\n blankVisibleRatio: tuning?.blankVisibleRatio ?? defaults.blankVisibleRatio,\n blankAverageColor: tuning?.blankAverageColor ?? defaults.blankAverageColor,\n };\n}\n\nfunction waitForNextAnimationFrame() {\n return new Promise<void>((resolve) => {\n requestAnimationFrame(() => resolve());\n });\n}\n\nfunction isMostlyBlankCapture(\n pixels: Uint8Array,\n tuning: ResolvedSparkSplatCaptureTuning\n) {\n const sampleCount = Math.max(1, Math.floor(tuning.blankSampleCount));\n const stride = Math.max(4, Math.floor(pixels.length / sampleCount / 4) * 4);\n let sampled = 0;\n let visible = 0;\n let colorTotal = 0;\n\n for (let i = 0; i < pixels.length; i += stride) {\n const alpha = pixels[i + 3];\n const color = pixels[i] + pixels[i + 1] + pixels[i + 2];\n sampled += 1;\n if (alpha > tuning.blankAlphaThreshold) visible += 1;\n colorTotal += color;\n }\n\n if (sampled === 0) return true;\n return (\n visible / sampled < tuning.blankVisibleRatio ||\n colorTotal / sampled < tuning.blankAverageColor\n );\n}\n\nexport interface SparkSplatLifecycle {\n status: SparkSplatStatus;\n error: Error | null;\n isLoading: boolean;\n isReady: boolean;\n isError: boolean;\n props: Pick<SparkSplatEnvironmentProps, 'onStatusChange' | 'onError'>;\n reset: () => void;\n}\n\nexport interface SparkSplatEnvironmentState {\n environment: PairedSplatEnvironmentConfig | undefined;\n sceneConfig: SceneConfig;\n readiness: SplatEnvironmentReadiness;\n lifecycle: SparkSplatLifecycle;\n props: Pick<\n SparkSplatEnvironmentProps,\n 'environment' | 'scenario' | 'src' | 'format' | 'onStatusChange' | 'onError'\n >;\n enabled: boolean;\n}\n\nexport interface SparkSplatEnvironmentProps extends SplatEnvironmentProps {\n /** Enable Spark LoD handling for large splat assets. Default: true. */\n lod?: boolean | 'quality';\n /** Tune Spark's live viewport renderer. Defaults favor interactive FPS. */\n renderTuning?: SparkSplatRenderTuning;\n /** Tune Spark camera-frame capture. Defaults favor sharper snapshots. */\n captureTuning?: SparkSplatCaptureTuning;\n /**\n * Hide meshes whose names include floor, ground, or plane while the splat is\n * active. This mirrors the common hybrid-rendering setup where MJCF keeps\n * collision geometry but the splat owns the visual environment.\n */\n hideGroundMeshes?: boolean;\n onStatusChange?: (status: SparkSplatStatus) => void;\n onLoad?: (mesh: SparkSplatMeshInstance) => void;\n onError?: (error: Error) => void;\n}\n\n/**\n * Resolve a visual scenario's paired splat environment, compose its MJCF\n * collision proxy into the MuJoCo scene config, and expose Spark lifecycle\n * props for `<SparkSplatEnvironment />`.\n */\nexport function useSparkSplatEnvironment({\n sceneConfig,\n scenario,\n environment,\n enabled = true,\n renderer = 'spark',\n onError,\n onStatusChange,\n}: {\n sceneConfig: SceneConfig;\n scenario?: VisualScenarioConfig;\n environment?: PairedSplatEnvironmentConfig;\n enabled?: boolean;\n renderer?: 'spark';\n onError?: (error: Error) => void;\n onStatusChange?: (status: SparkSplatStatus) => void;\n}): SparkSplatEnvironmentState {\n const splatScene = useSplatSceneConfig({\n sceneConfig,\n scenario,\n environment,\n enabled,\n renderer,\n });\n const metadata = useSplatEnvironment({\n scenario,\n environment: splatScene.environment,\n renderer,\n });\n const renderEnabled = enabled && Boolean(metadata.src);\n const readiness = enabled ? metadata.readiness : splatScene.readiness;\n const lifecycle = useSparkSplatLifecycle({\n enabled: renderEnabled,\n onError,\n onStatusChange,\n });\n\n return useMemo(\n () => ({\n environment: splatScene.environment,\n sceneConfig: splatScene.sceneConfig,\n readiness,\n lifecycle,\n props: {\n environment: splatScene.environment,\n scenario: enabled ? scenario : undefined,\n src: enabled ? metadata.src : undefined,\n format: metadata.format,\n ...lifecycle.props,\n },\n enabled: renderEnabled,\n }),\n [enabled, lifecycle, metadata, readiness, renderEnabled, scenario, splatScene]\n );\n}\n\n/**\n * Tracks Spark 3DGS loading state for UI that wraps `SparkSplatEnvironment`.\n *\n * Use the returned `props` with `<SparkSplatEnvironment {...lifecycle.props} />`\n * to avoid repeating status/error state in app code.\n */\nexport function useSparkSplatLifecycle({\n enabled = true,\n initialStatus,\n onError,\n onStatusChange,\n}: {\n enabled?: boolean;\n initialStatus?: SparkSplatStatus;\n onError?: (error: Error) => void;\n onStatusChange?: (status: SparkSplatStatus) => void;\n} = {}): SparkSplatLifecycle {\n const [status, setStatus] = useState<SparkSplatStatus>(\n initialStatus ?? (enabled ? 'loading' : 'idle')\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n setStatus(enabled ? initialStatus ?? 'loading' : 'idle');\n setError(null);\n }, [enabled, initialStatus]);\n\n const handleStatusChange = useCallback(\n (nextStatus: SparkSplatStatus) => {\n setStatus(nextStatus);\n if (nextStatus !== 'error') {\n setError(null);\n }\n onStatusChange?.(nextStatus);\n },\n [onStatusChange]\n );\n\n const handleError = useCallback(\n (nextError: Error) => {\n setError(nextError);\n setStatus('error');\n onError?.(nextError);\n },\n [onError]\n );\n\n const reset = useCallback(() => {\n setStatus(enabled ? initialStatus ?? 'loading' : 'idle');\n setError(null);\n }, [enabled, initialStatus]);\n\n return useMemo(\n () => ({\n status,\n error,\n isLoading: status === 'loading',\n isReady: status === 'ready',\n isError: status === 'error',\n props: {\n onStatusChange: handleStatusChange,\n onError: handleError,\n },\n reset,\n }),\n [error, handleError, handleStatusChange, reset, status]\n );\n}\n\n/**\n * Optional SparkJS-backed Gaussian splat renderer for React Three Fiber scenes.\n *\n * Import from `mujoco-react/spark` and install `@sparkjsdev/spark` in the app\n * that uses it. The core `mujoco-react` entrypoint does not depend on Spark.\n */\nexport function SparkSplatEnvironment({\n environment,\n scenario,\n renderer = 'spark',\n src,\n format,\n collisionProxy,\n collisionProxyMetadata,\n showPlaceholder,\n children,\n lod = true,\n renderTuning,\n captureTuning,\n hideGroundMeshes = false,\n onStatusChange,\n onLoad,\n onError,\n ...groupProps\n}: SparkSplatEnvironmentProps) {\n const groupRef = useRef<THREE.Group>(null);\n const sparkRef = useRef<SparkRendererInstance | null>(null);\n const captureSparkRef = useRef<SparkCaptureRenderer | null>(null);\n const meshRef = useRef<SparkSplatMeshInstance | null>(null);\n const hiddenMeshesRef = useRef<THREE.Mesh[]>([]);\n const onStatusChangeRef = useRef(onStatusChange);\n const onLoadRef = useRef(onLoad);\n const onErrorRef = useRef(onError);\n const [status, setStatus] = useState<SparkSplatStatus>('idle');\n const { gl, invalidate } = useThree();\n const metadata = useSplatEnvironment({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy: collisionProxyMetadata,\n });\n const resolvedRenderTuning = useMemo(\n () => resolveRenderTuning(getDefaultLiveRenderTuning(lod), renderTuning),\n [\n lod,\n renderTuning?.lodRenderScale,\n renderTuning?.lodSplatScale,\n renderTuning?.minSortIntervalMs,\n ]\n );\n const resolvedCaptureTuning = useMemo(\n () => resolveCaptureTuning(lod, captureTuning),\n [\n captureTuning?.blankAlphaThreshold,\n captureTuning?.blankAverageColor,\n captureTuning?.blankSampleCount,\n captureTuning?.blankVisibleRatio,\n captureTuning?.lodRenderScale,\n captureTuning?.lodSplatScale,\n captureTuning?.maxWarmupFrames,\n captureTuning?.minSortIntervalMs,\n lod,\n ]\n );\n\n useEffect(() => {\n onStatusChangeRef.current = onStatusChange;\n }, [onStatusChange]);\n\n useEffect(() => {\n onLoadRef.current = onLoad;\n }, [onLoad]);\n\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n useEffect(() => {\n let disposed = false;\n ensureSparkDisposeRejectionHandler();\n\n function setLifecycleStatus(nextStatus: SparkSplatStatus) {\n setStatus(nextStatus);\n onStatusChangeRef.current?.(nextStatus);\n }\n\n function restoreHiddenMeshes() {\n for (const mesh of hiddenMeshesRef.current) {\n mesh.visible = true;\n }\n hiddenMeshesRef.current = [];\n }\n\n function disposeCaptureSpark() {\n safelyDisposeSparkResource(captureSparkRef.current?.renderer);\n captureSparkRef.current = null;\n }\n\n async function loadSplat() {\n if (!metadata.src) {\n setLifecycleStatus('idle');\n return;\n }\n\n if (metadata.format !== 'spz') {\n const unsupportedFormatError = new Error(\n `SparkSplatEnvironment only supports .spz assets; received \"${metadata.format}\".`\n );\n setLifecycleStatus('error');\n onErrorRef.current?.(unsupportedFormatError);\n return;\n }\n\n setLifecycleStatus('loading');\n\n try {\n const sparkModule = await import('@sparkjsdev/spark');\n if (disposed || !groupRef.current) return;\n\n const spark = new sparkModule.SparkRenderer({\n renderer: gl,\n onDirty: invalidate,\n ...resolvedRenderTuning,\n });\n spark.userData[CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY] = async ({\n scene,\n camera,\n width,\n height,\n }: CameraFrameCaptureRenderInput): Promise<CameraFrameCaptureRenderResult> => {\n let captureSpark = captureSparkRef.current;\n if (\n !captureSpark ||\n captureSpark.width !== width ||\n captureSpark.height !== height\n ) {\n disposeCaptureSpark();\n captureSpark = {\n renderer: new sparkModule.SparkRenderer({\n renderer: gl,\n autoUpdate: false,\n lodSplatScale: resolvedCaptureTuning.lodSplatScale,\n lodRenderScale: resolvedCaptureTuning.lodRenderScale,\n minSortIntervalMs: resolvedCaptureTuning.minSortIntervalMs,\n target: {\n width,\n height,\n },\n }),\n width,\n height,\n };\n captureSparkRef.current = captureSpark;\n }\n\n const captureRenderer = captureSpark.renderer;\n const maxWarmupFrames = Math.max(\n 1,\n Math.floor(resolvedCaptureTuning.maxWarmupFrames)\n );\n let pixels: Uint8Array | undefined;\n for (let attempt = 0; attempt < maxWarmupFrames; attempt += 1) {\n captureRenderer.renderSize.set(width, height);\n await captureRenderer.update({ scene, camera });\n pixels = await captureRenderer.renderReadTarget({\n scene,\n camera,\n });\n if (\n captureRenderer.activeSplats > 0 &&\n !isMostlyBlankCapture(pixels, resolvedCaptureTuning)\n ) {\n break;\n }\n if (attempt < maxWarmupFrames - 1) {\n await waitForNextAnimationFrame();\n }\n }\n if (!pixels) {\n throw new Error('Spark camera frame capture did not produce pixels.');\n }\n return { pixels, width, height, flipY: true };\n };\n const mesh = new sparkModule.SplatMesh({\n url: metadata.src,\n lod,\n });\n mesh.name = 'GaussianSplatMesh';\n\n groupRef.current.add(spark);\n groupRef.current.add(mesh);\n sparkRef.current = spark;\n meshRef.current = mesh;\n\n if (hideGroundMeshes && groupRef.current.parent) {\n groupRef.current.parent.traverse((object) => {\n if (\n !(object instanceof THREE.Mesh) ||\n object === (mesh as unknown as THREE.Object3D)\n ) {\n return;\n }\n const name = object.name.toLowerCase();\n if (\n name.includes('floor') ||\n name.includes('ground') ||\n name.includes('plane')\n ) {\n object.visible = false;\n hiddenMeshesRef.current.push(object);\n }\n });\n }\n\n await mesh.initialized;\n if (disposed) return;\n setLifecycleStatus('ready');\n onLoadRef.current?.(mesh);\n invalidate();\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error));\n setLifecycleStatus('error');\n onErrorRef.current?.(normalizedError);\n }\n }\n\n void loadSplat();\n\n return () => {\n disposed = true;\n restoreHiddenMeshes();\n\n if (meshRef.current) {\n groupRef.current?.remove(meshRef.current);\n safelyDisposeSparkResource(meshRef.current);\n meshRef.current = null;\n }\n\n if (sparkRef.current) {\n groupRef.current?.remove(sparkRef.current);\n safelyDisposeSparkResource(sparkRef.current);\n sparkRef.current = null;\n }\n\n disposeCaptureSpark();\n };\n }, [\n gl,\n hideGroundMeshes,\n invalidate,\n lod,\n metadata.format,\n metadata.src,\n resolvedCaptureTuning.blankAlphaThreshold,\n resolvedCaptureTuning.blankAverageColor,\n resolvedCaptureTuning.blankSampleCount,\n resolvedCaptureTuning.blankVisibleRatio,\n resolvedCaptureTuning.lodRenderScale,\n resolvedCaptureTuning.lodSplatScale,\n resolvedCaptureTuning.maxWarmupFrames,\n resolvedCaptureTuning.minSortIntervalMs,\n resolvedRenderTuning.lodRenderScale,\n resolvedRenderTuning.lodSplatScale,\n resolvedRenderTuning.minSortIntervalMs,\n ]);\n\n return (\n <SplatEnvironment\n {...groupProps}\n environment={environment}\n scenario={scenario}\n renderer={renderer}\n src={metadata.src}\n format={metadata.format}\n collisionProxyMetadata={metadata.collisionProxy}\n collisionProxy={collisionProxy}\n showPlaceholder={showPlaceholder ?? status !== 'ready'}\n >\n <group ref={groupRef} />\n {children}\n </SplatEnvironment>\n );\n}\n\nfunction safelyDisposeSparkResource(\n resource: SparkDisposable | null | undefined\n) {\n if (!resource) return;\n try {\n silenceSparkWorkerTerminateRejections(resource);\n const result = resource.dispose?.();\n if (isPromiseLike(result)) {\n void Promise.resolve(result).catch(handleSparkDisposeError);\n }\n } catch (error) {\n handleSparkDisposeError(error);\n }\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'then' in value &&\n typeof (value as { then?: unknown }).then === 'function'\n );\n}\n\nfunction silenceSparkWorkerTerminateRejections(resource: SparkDisposable) {\n const workers = getSparkWorkers(resource);\n for (const worker of workers) {\n if (!worker.messages) continue;\n\n for (const message of Object.values(worker.messages)) {\n const reject = message.reject;\n if (!reject) continue;\n\n message.reject = (error: unknown) => {\n if (!isSparkWorkerTerminateError(error)) {\n reject(error);\n }\n };\n }\n }\n}\n\nfunction getSparkWorkers(resource: SparkDisposable): SparkWorkerLike[] {\n const sparkResource = resource as SparkResourceWithWorkers;\n return [\n sparkResource.worker,\n sparkResource.sortWorker,\n sparkResource.lodWorker,\n ].filter((worker): worker is SparkWorkerLike => Boolean(worker));\n}\n\nfunction handleSparkDisposeError(error: unknown) {\n if (\n error instanceof Error &&\n error.message.toLowerCase().includes('worker terminate')\n ) {\n return;\n }\n\n console.warn('[mujoco-react] Spark resource disposal failed.', error);\n}\n\nfunction ensureSparkDisposeRejectionHandler() {\n if (\n sparkDisposeRejectionHandlerRegistered ||\n typeof window === 'undefined' ||\n typeof window.addEventListener !== 'function'\n ) {\n return;\n }\n\n sparkDisposeRejectionHandlerRegistered = true;\n window.addEventListener('unhandledrejection', (event) => {\n if (isSparkWorkerTerminateError(event.reason)) {\n event.preventDefault();\n }\n });\n}\n\nfunction isSparkWorkerTerminateError(reason: unknown) {\n return (\n reason instanceof Error &&\n reason.message.toLowerCase().includes('worker terminate')\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import { useFrame } from '@react-three/fiber';
|
|
|
13
13
|
import type { ThreeElements } from '@react-three/fiber';
|
|
14
14
|
import * as THREE from 'three';
|
|
15
15
|
import { useMujocoContext } from '../core/MujocoSimProvider';
|
|
16
|
+
import { CAPTURE_EXCLUDE_KEY } from '../rendering/cameraFrameCapture';
|
|
16
17
|
import { getContact, withContacts } from '../types';
|
|
17
18
|
|
|
18
19
|
const _dummy = new THREE.Object3D();
|
|
@@ -70,7 +71,13 @@ export function ContactMarkers({
|
|
|
70
71
|
if (status !== 'ready') return null;
|
|
71
72
|
|
|
72
73
|
return (
|
|
73
|
-
<group
|
|
74
|
+
<group
|
|
75
|
+
{...groupProps}
|
|
76
|
+
userData={{
|
|
77
|
+
...groupProps.userData,
|
|
78
|
+
[CAPTURE_EXCLUDE_KEY]: true,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
74
81
|
<instancedMesh ref={meshRef} args={[undefined, undefined, maxContacts]} frustumCulled={false} renderOrder={999}>
|
|
75
82
|
<sphereGeometry args={[radius, 8, 8]} />
|
|
76
83
|
<meshBasicMaterial color={color} depthTest={false} />
|