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/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-BuJ4boaq.js';
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-VDSEPZYQ.js';
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":"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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 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"]}
@@ -643,6 +643,7 @@ interface DebugProps {
643
643
  showGeoms?: boolean;
644
644
  showSites?: boolean;
645
645
  showJoints?: boolean;
646
+ showCameras?: boolean;
646
647
  showContacts?: boolean;
647
648
  showCOM?: boolean;
648
649
  showInertia?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "9.4.0",
3
+ "version": "9.5.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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 {...groupProps}>
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} />