mujoco-react 8.10.0 → 8.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 { l as SplatEnvironmentProps } from './types-FFW7ykBu.js';
3
+ import { l as SplatEnvironmentProps } from './types-BmneHLBM.js';
4
4
  import 'react';
5
5
  import '@react-three/fiber';
6
6
  import 'three';
@@ -8,6 +8,15 @@ 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 SparkSplatLifecycle {
12
+ status: SparkSplatStatus;
13
+ error: Error | null;
14
+ isLoading: boolean;
15
+ isReady: boolean;
16
+ isError: boolean;
17
+ props: Pick<SparkSplatEnvironmentProps, 'onStatusChange' | 'onError'>;
18
+ reset: () => void;
19
+ }
11
20
  interface SparkSplatEnvironmentProps extends SplatEnvironmentProps {
12
21
  /** Enable Spark LoD handling for large splat assets. Default: true. */
13
22
  lod?: boolean | 'quality';
@@ -21,12 +30,24 @@ interface SparkSplatEnvironmentProps extends SplatEnvironmentProps {
21
30
  onLoad?: (mesh: SparkSplatMeshInstance) => void;
22
31
  onError?: (error: Error) => void;
23
32
  }
33
+ /**
34
+ * Tracks Spark 3DGS loading state for UI that wraps `SparkSplatEnvironment`.
35
+ *
36
+ * Use the returned `props` with `<SparkSplatEnvironment {...lifecycle.props} />`
37
+ * to avoid repeating status/error state in app code.
38
+ */
39
+ declare function useSparkSplatLifecycle({ enabled, initialStatus, onError, onStatusChange, }?: {
40
+ enabled?: boolean;
41
+ initialStatus?: SparkSplatStatus;
42
+ onError?: (error: Error) => void;
43
+ onStatusChange?: (status: SparkSplatStatus) => void;
44
+ }): SparkSplatLifecycle;
24
45
  /**
25
46
  * Optional SparkJS-backed Gaussian splat renderer for React Three Fiber scenes.
26
47
  *
27
48
  * Import from `mujoco-react/spark` and install `@sparkjsdev/spark` in the app
28
49
  * that uses it. The core `mujoco-react` entrypoint does not depend on Spark.
29
50
  */
30
- declare function SparkSplatEnvironment({ environment, src, format, collisionProxy, collisionProxyMetadata, showPlaceholder, children, lod, hideGroundMeshes, onStatusChange, onLoad, onError, ...groupProps }: SparkSplatEnvironmentProps): react_jsx_runtime.JSX.Element;
51
+ declare function SparkSplatEnvironment({ environment, scenario, renderer, src, format, collisionProxy, collisionProxyMetadata, showPlaceholder, children, lod, hideGroundMeshes, onStatusChange, onLoad, onError, ...groupProps }: SparkSplatEnvironmentProps): react_jsx_runtime.JSX.Element;
31
52
 
32
- export { SparkSplatEnvironment, type SparkSplatEnvironmentProps, type SparkSplatStatus };
53
+ export { SparkSplatEnvironment, type SparkSplatEnvironmentProps, type SparkSplatLifecycle, type SparkSplatStatus, useSparkSplatLifecycle };
package/dist/spark.js CHANGED
@@ -1,11 +1,65 @@
1
- import { useSplatEnvironment, SplatEnvironment } from './chunk-KGFRKPLS.js';
1
+ import { useSplatEnvironment, SplatEnvironment } from './chunk-SEWQULWO.js';
2
2
  import { useThree } from '@react-three/fiber';
3
- import { useRef, useState, useEffect } from 'react';
3
+ import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
4
4
  import * as THREE from 'three';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
6
 
7
+ function useSparkSplatLifecycle({
8
+ enabled = true,
9
+ initialStatus,
10
+ onError,
11
+ onStatusChange
12
+ } = {}) {
13
+ const [status, setStatus] = useState(
14
+ initialStatus ?? (enabled ? "loading" : "idle")
15
+ );
16
+ const [error, setError] = useState(null);
17
+ useEffect(() => {
18
+ setStatus(enabled ? initialStatus ?? "loading" : "idle");
19
+ setError(null);
20
+ }, [enabled, initialStatus]);
21
+ const handleStatusChange = useCallback(
22
+ (nextStatus) => {
23
+ setStatus(nextStatus);
24
+ if (nextStatus !== "error") {
25
+ setError(null);
26
+ }
27
+ onStatusChange?.(nextStatus);
28
+ },
29
+ [onStatusChange]
30
+ );
31
+ const handleError = useCallback(
32
+ (nextError) => {
33
+ setError(nextError);
34
+ setStatus("error");
35
+ onError?.(nextError);
36
+ },
37
+ [onError]
38
+ );
39
+ const reset = useCallback(() => {
40
+ setStatus(enabled ? initialStatus ?? "loading" : "idle");
41
+ setError(null);
42
+ }, [enabled, initialStatus]);
43
+ return useMemo(
44
+ () => ({
45
+ status,
46
+ error,
47
+ isLoading: status === "loading",
48
+ isReady: status === "ready",
49
+ isError: status === "error",
50
+ props: {
51
+ onStatusChange: handleStatusChange,
52
+ onError: handleError
53
+ },
54
+ reset
55
+ }),
56
+ [error, handleError, handleStatusChange, reset, status]
57
+ );
58
+ }
7
59
  function SparkSplatEnvironment({
8
60
  environment,
61
+ scenario,
62
+ renderer = "spark",
9
63
  src,
10
64
  format,
11
65
  collisionProxy,
@@ -30,6 +84,8 @@ function SparkSplatEnvironment({
30
84
  const { gl, invalidate } = useThree();
31
85
  const metadata = useSplatEnvironment({
32
86
  environment,
87
+ scenario,
88
+ renderer,
33
89
  src,
34
90
  format,
35
91
  collisionProxy: collisionProxyMetadata
@@ -56,10 +112,18 @@ function SparkSplatEnvironment({
56
112
  hiddenMeshesRef.current = [];
57
113
  }
58
114
  async function loadSplat() {
59
- if (!metadata.src || metadata.format !== "spz") {
115
+ if (!metadata.src) {
60
116
  setLifecycleStatus("idle");
61
117
  return;
62
118
  }
119
+ if (metadata.format !== "spz") {
120
+ const unsupportedFormatError = new Error(
121
+ `SparkSplatEnvironment only supports .spz assets; received "${metadata.format}".`
122
+ );
123
+ setLifecycleStatus("error");
124
+ onErrorRef.current?.(unsupportedFormatError);
125
+ return;
126
+ }
63
127
  setLifecycleStatus("loading");
64
128
  try {
65
129
  const sparkModule = await import('@sparkjsdev/spark');
@@ -106,12 +170,12 @@ function SparkSplatEnvironment({
106
170
  restoreHiddenMeshes();
107
171
  if (meshRef.current) {
108
172
  groupRef.current?.remove(meshRef.current);
109
- meshRef.current.dispose?.();
173
+ safelyDisposeSparkResource(meshRef.current);
110
174
  meshRef.current = null;
111
175
  }
112
176
  if (sparkRef.current) {
113
177
  groupRef.current?.remove(sparkRef.current);
114
- sparkRef.current.dispose?.();
178
+ safelyDisposeSparkResource(sparkRef.current);
115
179
  sparkRef.current = null;
116
180
  }
117
181
  };
@@ -128,6 +192,8 @@ function SparkSplatEnvironment({
128
192
  {
129
193
  ...groupProps,
130
194
  environment,
195
+ scenario,
196
+ renderer,
131
197
  src: metadata.src,
132
198
  format: metadata.format,
133
199
  collisionProxyMetadata: metadata.collisionProxy,
@@ -140,11 +206,30 @@ function SparkSplatEnvironment({
140
206
  }
141
207
  );
142
208
  }
209
+ function safelyDisposeSparkResource(resource) {
210
+ try {
211
+ const result = resource.dispose?.();
212
+ if (isPromiseLike(result)) {
213
+ void Promise.resolve(result).catch(handleSparkDisposeError);
214
+ }
215
+ } catch (error) {
216
+ handleSparkDisposeError(error);
217
+ }
218
+ }
219
+ function isPromiseLike(value) {
220
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
221
+ }
222
+ function handleSparkDisposeError(error) {
223
+ if (error instanceof Error && error.message.toLowerCase().includes("worker terminate")) {
224
+ return;
225
+ }
226
+ console.warn("[mujoco-react] Spark resource disposal failed.", error);
227
+ }
143
228
  /**
144
229
  * @license
145
230
  * SPDX-License-Identifier: Apache-2.0
146
231
  */
147
232
 
148
- export { SparkSplatEnvironment };
233
+ export { SparkSplatEnvironment, useSparkSplatLifecycle };
149
234
  //# sourceMappingURL=spark.js.map
150
235
  //# sourceMappingURL=spark.js.map
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 { useEffect, useRef, useState } from 'react';\nimport * as THREE from 'three';\nimport {\n SplatEnvironment,\n useSplatEnvironment,\n} from './components/VisualScenario';\nimport type {\n SplatEnvironmentProps,\n} from './types';\n\ntype SparkModule = typeof import('@sparkjsdev/spark');\ntype SparkRendererInstance = InstanceType<SparkModule['SparkRenderer']>;\ntype SparkSplatMeshInstance = InstanceType<SparkModule['SplatMesh']>;\n\nexport type SparkSplatStatus = 'idle' | 'loading' | 'ready' | 'error';\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 * 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 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 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\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 || metadata.format !== 'spz') {\n setLifecycleStatus('idle');\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 meshRef.current.dispose?.();\n meshRef.current = null;\n }\n\n if (sparkRef.current) {\n groupRef.current?.remove(sparkRef.current);\n sparkRef.current.dispose?.();\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 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"]}
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} from './components/VisualScenario';\nimport type {\n SplatEnvironmentProps,\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};\n\nexport type SparkSplatStatus = 'idle' | 'loading' | 'ready' | 'error';\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 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 * 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\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 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 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"]}
@@ -309,6 +309,8 @@ interface SceneObject {
309
309
  solref?: string;
310
310
  solimp?: string;
311
311
  condim?: number;
312
+ /** MuJoCo geom group. Group 3 is conventionally used for collision-only helper geoms. */
313
+ group?: number;
312
314
  }
313
315
  interface XmlPatch {
314
316
  target: string;
@@ -320,6 +322,8 @@ type LocalMujocoFile = File;
320
322
  interface LoadFromFilesOptions {
321
323
  /** Entry MJCF/URDF file. Inferred from scene.xml, model.xml, robot.xml, or the first XML/URDF file when omitted. */
322
324
  sceneFile?: string;
325
+ /** Additional MJCF environment XML files merged into the entry scene before MuJoCo compilation. */
326
+ environmentFiles?: string[];
323
327
  homeJoints?: number[];
324
328
  xmlPatches?: XmlPatch[];
325
329
  sceneObjects?: SceneObject[];
@@ -332,6 +336,14 @@ interface SceneConfig {
332
336
  sceneFile: string;
333
337
  /** Browser-selected files for local MJCF/URDF loading. Preserves webkitRelativePath when available. */
334
338
  files?: readonly LocalMujocoFile[];
339
+ /**
340
+ * Additional MJCF environment XML files merged into the entry scene before compilation.
341
+ *
342
+ * Use this for static collision/physics layers such as a Gaussian-splat
343
+ * environment's proxy `scene.xml`; render the splat itself as a separate
344
+ * visual layer.
345
+ */
346
+ environmentFiles?: string[];
335
347
  sceneObjects?: SceneObject[];
336
348
  homeJoints?: number[];
337
349
  xmlPatches?: XmlPatch[];
@@ -524,10 +536,24 @@ interface KeyboardTeleopConfig {
524
536
  bindings: Record<string, KeyBinding>;
525
537
  enabled?: boolean;
526
538
  }
539
+ type PolicyVector = Float32Array | Float64Array | number[];
540
+ interface PolicyObservationInput {
541
+ model: MujocoModel;
542
+ data: MujocoData;
543
+ }
544
+ interface PolicyInferenceInput extends PolicyObservationInput {
545
+ observation: PolicyVector;
546
+ }
547
+ interface PolicyActionInput extends PolicyInferenceInput {
548
+ action: PolicyVector;
549
+ }
527
550
  interface PolicyConfig {
528
551
  frequency: number;
529
- onObservation: (model: MujocoModel, data: MujocoData) => Float32Array | Float64Array | number[];
530
- onAction: (action: Float32Array | Float64Array | number[], model: MujocoModel, data: MujocoData) => void;
552
+ enabled?: boolean;
553
+ onObservation: (input: PolicyObservationInput) => PolicyVector;
554
+ /** Run policy inference. Omit to pass observations directly to `onAction` for custom inline controllers. */
555
+ infer?: (input: PolicyInferenceInput) => PolicyVector;
556
+ onAction: (input: PolicyActionInput) => void;
531
557
  }
532
558
  type ObservationOutput = 'float32' | 'float64';
533
559
  interface ObservationConfig {
@@ -600,6 +626,12 @@ interface ScenarioCameraConfig {
600
626
  noise?: number;
601
627
  blur?: number;
602
628
  }
629
+ interface ScenarioMaterialConfig {
630
+ randomizeObjectColors?: boolean;
631
+ randomizeTableMaterial?: boolean;
632
+ roughness?: number;
633
+ metalness?: number;
634
+ }
603
635
  interface SplatAssetConfig {
604
636
  src: string;
605
637
  /** Common browser-friendly splat format. Renderer-specific loaders may accept more. */
@@ -613,7 +645,7 @@ interface SplatScenarioConfig {
613
645
  format?: SplatFormat;
614
646
  src?: string;
615
647
  requiresCollisionProxy?: boolean;
616
- collisionProxy?: SplatCollisionProxyConfig;
648
+ collisionProxy?: SplatCollisionProxyConfig | null;
617
649
  }
618
650
  interface SplatCollisionProxyConfig {
619
651
  /** MJCF/XML file or artifact path that provides physics collision for the visual splat. */
@@ -638,6 +670,8 @@ interface PairedSplatEnvironmentConfig {
638
670
  }
639
671
  interface SplatEnvironmentMetadataInput {
640
672
  environment?: PairedSplatEnvironmentConfig;
673
+ scenario?: VisualScenarioConfig;
674
+ renderer?: SplatRendererKind;
641
675
  src?: string;
642
676
  format?: SplatFormat;
643
677
  collisionProxy?: SplatCollisionProxyConfig;
@@ -648,6 +682,7 @@ interface SplatEnvironmentMetadata {
648
682
  collisionProxy?: SplatCollisionProxyConfig;
649
683
  userData: Record<string, unknown>;
650
684
  }
685
+ type SplatSceneInput = PairedSplatEnvironmentConfig | VisualScenarioConfig | undefined | null;
651
686
  interface VisualScenarioConfig {
652
687
  id?: string;
653
688
  label?: string;
@@ -655,7 +690,8 @@ interface VisualScenarioConfig {
655
690
  lighting?: ScenarioLightingPreset;
656
691
  environment?: string;
657
692
  camera?: ScenarioCameraConfig;
658
- splat?: SplatScenarioConfig;
693
+ materials?: ScenarioMaterialConfig;
694
+ splat?: SplatScenarioConfig | null;
659
695
  }
660
696
  interface ScenarioLightingProps {
661
697
  preset?: ScenarioLightingPreset;
@@ -664,12 +700,26 @@ interface ScenarioLightingProps {
664
700
  }
665
701
  interface SplatEnvironmentProps extends Omit<ThreeElements['group'], 'ref'> {
666
702
  environment?: PairedSplatEnvironmentConfig;
703
+ scenario?: VisualScenarioConfig;
704
+ renderer?: SplatRendererKind;
667
705
  src?: string;
668
706
  format?: SplatFormat;
669
707
  collisionProxy?: ReactNode;
670
708
  collisionProxyMetadata?: SplatCollisionProxyConfig;
671
709
  showPlaceholder?: boolean;
672
710
  }
711
+ interface VisualScenarioEffectsProps {
712
+ scenario?: VisualScenarioConfig;
713
+ enabled?: boolean;
714
+ applyBackground?: boolean;
715
+ applyFog?: boolean;
716
+ applyRenderer?: boolean;
717
+ applyMaterials?: boolean;
718
+ background?: THREE.ColorRepresentation;
719
+ fogNear?: number;
720
+ fogFar?: number;
721
+ materialFilter?: (object: THREE.Object3D, material: THREE.Material) => boolean;
722
+ }
673
723
  type TrajectoryInput = TrajectoryFrame[] | number[][];
674
724
  interface TrajectoryPlayerProps {
675
725
  trajectory: TrajectoryInput;
@@ -699,6 +749,8 @@ interface BodyProps {
699
749
  solref?: string;
700
750
  solimp?: string;
701
751
  condim?: number;
752
+ /** MuJoCo geom group. Group 3 is conventionally used for collision-only helper geoms. */
753
+ group?: number;
702
754
  children?: ReactNode;
703
755
  }
704
756
  interface MujocoSimAPI {
@@ -760,6 +812,8 @@ interface MujocoSimAPI {
760
812
  }
761
813
  type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
762
814
  config: SceneConfig;
815
+ /** R3F content rendered while the MuJoCo WASM module is still loading. */
816
+ loadingFallback?: ReactNode;
763
817
  onReady?: (api: MujocoSimAPI) => void;
764
818
  onError?: (error: Error) => void;
765
819
  onStep?: (time: number) => void;
@@ -814,4 +868,4 @@ interface JointStateResult {
814
868
  velocity: React__default.RefObject<number | Float64Array>;
815
869
  }
816
870
 
817
- export { type BodyInfo as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type SensorHandle as E, type SensorInfo as F, type GeomInfo as G, type JointStateResult as H, type IkConfig as I, type Joints as J, type Bodies as K, type BodyStateResult as L, type MujocoContextValue as M, type Actuators as N, type ObservationConfig as O, type PhysicsStepCallback as P, type CtrlHandle as Q, type ContactInfo as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type KeyboardTeleopConfig as U, type VisualScenarioConfig as V, type PolicyConfig as W, type ObservationHandle as X, type TrajectoryInput as Y, type PlaybackState as Z, type TrajectoryFrame as _, type MujocoCanvasProps as a, type ControlJointInfo as a0, type Geoms as a1, type IKSolveFn as a2, type JointInfo as a3, type KeyBinding as a4, type Keyframes as a5, type ModelOptions as a6, type MujocoContact as a7, type MujocoContactArray as a8, type ObservationLayoutItem as a9, getContact as aA, registerRobotResources as aB, type ObservationOutput as aa, type PhysicsConfig as ab, type RayHit as ac, type Register as ad, type RegisteredRobotMap as ae, type ResourceSelector as af, RobotActuators as ag, RobotBodies as ah, RobotGeoms as ai, RobotJoints as aj, RobotKeyframes as ak, type RobotResource as al, RobotResources as am, RobotSensors as an, RobotSites as ao, type Robots as ap, type ScenarioCameraConfig as aq, type SceneMarker as ar, type SceneObject as as, type SensorResult as at, type SiteInfo as au, type SplatAssetConfig as av, type SplatScenarioConfig as aw, type StateSnapshot as ax, type TrajectoryData as ay, type XmlPatch as az, type MujocoSimAPI as b, type MujocoModule as c, type MujocoModel as d, type MujocoData as e, type ControlGroupSelector as f, type ObservationResult as g, type IkContextValue as h, type IkGizmoProps as i, type SceneLightsProps as j, type ScenarioLightingProps as k, type SplatEnvironmentProps as l, type PairedSplatEnvironmentConfig as m, type SplatFormat as n, type SplatCollisionProxyConfig as o, type SplatRendererKind as p, type SplatCollisionPrimitive as q, type ScenarioLightingPreset as r, type SplatEnvironmentMetadataInput as s, type SplatEnvironmentMetadata as t, type DebugProps as u, type ContactListenerProps as v, type ActuatorInfo as w, type Sites as x, type SitePositionResult as y, type Sensors as z };
871
+ export { type TrajectoryInput as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type SitePositionResult as E, type Sensors as F, type GeomInfo as G, type SensorHandle as H, type IkConfig as I, type SensorInfo as J, type Joints as K, type JointStateResult as L, type MujocoContextValue as M, type Bodies as N, type ObservationConfig as O, type PhysicsStepCallback as P, type BodyStateResult as Q, type Actuators as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type CtrlHandle as U, type VisualScenarioEffectsProps as V, type ContactInfo as W, type KeyboardTeleopConfig as X, type PolicyConfig as Y, type PolicyVector as Z, type ObservationHandle as _, type MujocoCanvasProps as a, type PlaybackState as a0, type TrajectoryFrame as a1, type BodyInfo as a2, type ControlJointInfo as a3, type Geoms as a4, type IKSolveFn as a5, type JointInfo as a6, type KeyBinding as a7, type Keyframes as a8, type ModelOptions as a9, type SensorResult as aA, type SiteInfo as aB, type SplatAssetConfig as aC, type SplatScenarioConfig as aD, type StateSnapshot as aE, type TrajectoryData as aF, type XmlPatch as aG, getContact as aH, registerRobotResources as aI, type MujocoContact as aa, type MujocoContactArray as ab, type ObservationLayoutItem as ac, type ObservationOutput as ad, type PhysicsConfig as ae, type PolicyActionInput as af, type PolicyInferenceInput as ag, type PolicyObservationInput as ah, type RayHit as ai, type Register as aj, type RegisteredRobotMap as ak, type ResourceSelector as al, RobotActuators as am, RobotBodies as an, RobotGeoms as ao, RobotJoints as ap, RobotKeyframes as aq, type RobotResource as ar, RobotResources as as, RobotSensors as at, RobotSites as au, type Robots as av, type ScenarioCameraConfig as aw, type ScenarioMaterialConfig as ax, type SceneMarker as ay, type SceneObject as az, type MujocoSimAPI as b, type MujocoModule as c, type MujocoModel as d, type MujocoData as e, type ControlGroupSelector as f, type ObservationResult as g, type IkContextValue as h, type IkGizmoProps as i, type SceneLightsProps as j, type ScenarioLightingProps as k, type SplatEnvironmentProps as l, type VisualScenarioConfig as m, type SplatRendererKind as n, type PairedSplatEnvironmentConfig as o, type SplatFormat as p, type SplatCollisionProxyConfig as q, type SplatCollisionPrimitive as r, type ScenarioLightingPreset as s, type SplatEnvironmentMetadataInput as t, type SplatEnvironmentMetadata as u, type SplatSceneInput as v, type DebugProps as w, type ContactListenerProps as x, type ActuatorInfo as y, type Sites as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "8.10.0",
3
+ "version": "8.11.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,6 +30,7 @@ export function Body({
30
30
  solref,
31
31
  solimp,
32
32
  condim,
33
+ group,
33
34
  children,
34
35
  }: BodyProps) {
35
36
  const { bodyRegistryRef, hiddenBodiesRef, requestBodyReload, mjDataRef, mjModelRef, status } =
@@ -53,6 +54,7 @@ export function Body({
53
54
  solref,
54
55
  solimp,
55
56
  condim,
57
+ group,
56
58
  };
57
59
  bodyRegistryRef.current.set(name, { definition, hasCustomChildren: hasChildren });
58
60
  if (hasChildren) {
@@ -66,7 +68,7 @@ export function Body({
66
68
  requestBodyReload();
67
69
  }
68
70
  };
69
- }, [name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, hasChildren, bodyRegistryRef, hiddenBodiesRef, requestBodyReload]);
71
+ }, [name, type, size, position, rgba, mass, freejoint, friction, solref, solimp, condim, group, hasChildren, bodyRegistryRef, hiddenBodiesRef, requestBodyReload]);
70
72
 
71
73
  // Resolve body ID once the scene is ready
72
74
  useEffect(() => {