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/README.md +65 -28
- package/dist/chunk-SEWQULWO.js +400 -0
- package/dist/chunk-SEWQULWO.js.map +1 -0
- package/dist/index.d.ts +82 -14
- package/dist/index.js +289 -17
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +24 -3
- package/dist/spark.js +91 -6
- package/dist/spark.js.map +1 -1
- package/dist/{types-FFW7ykBu.d.ts → types-BmneHLBM.d.ts} +59 -5
- package/package.json +1 -1
- package/src/components/Body.tsx +3 -1
- package/src/components/VisualScenario.tsx +343 -6
- package/src/core/MujocoCanvas.tsx +8 -1
- package/src/core/SceneLoader.ts +182 -3
- package/src/hooks/useFrameCapture.ts +206 -0
- package/src/hooks/usePolicy.ts +12 -8
- package/src/index.ts +25 -0
- package/src/spark.tsx +138 -4
- package/src/types.ts +69 -4
- package/dist/chunk-KGFRKPLS.js +0 -186
- package/dist/chunk-KGFRKPLS.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 { l as SplatEnvironmentProps } from './types-
|
|
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-
|
|
1
|
+
import { useSplatEnvironment, SplatEnvironment } from './chunk-SEWQULWO.js';
|
|
2
2
|
import { useThree } from '@react-three/fiber';
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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
|
|
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
package/src/components/Body.tsx
CHANGED
|
@@ -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(() => {
|