mujoco-react 8.10.0 → 9.0.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 +81 -44
- package/dist/chunk-33CV6HSV.js +400 -0
- package/dist/chunk-33CV6HSV.js.map +1 -0
- package/dist/index.d.ts +92 -24
- package/dist/index.js +338 -54
- 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-izZlUweI.d.ts} +109 -16
- package/package.json +1 -1
- package/src/components/Body.tsx +3 -1
- package/src/components/DragInteraction.tsx +1 -1
- package/src/components/IkGizmo.tsx +2 -2
- package/src/components/SceneRenderer.tsx +1 -1
- package/src/components/TrajectoryPlayer.tsx +4 -1
- package/src/components/VisualScenario.tsx +343 -6
- package/src/core/MujocoCanvas.tsx +8 -1
- package/src/core/MujocoPhysics.tsx +10 -4
- package/src/core/MujocoSimProvider.tsx +15 -12
- package/src/core/SceneLoader.ts +182 -3
- package/src/core/createController.tsx +2 -2
- package/src/hooks/useBodyState.ts +1 -1
- package/src/hooks/useContacts.ts +1 -1
- package/src/hooks/useCtrlNoise.ts +1 -1
- package/src/hooks/useFrameCapture.ts +206 -0
- package/src/hooks/useGamepad.ts +1 -1
- package/src/hooks/useGravityCompensation.ts +1 -1
- package/src/hooks/useIkController.ts +22 -13
- package/src/hooks/useJointState.ts +1 -1
- package/src/hooks/useKeyboardTeleop.ts +1 -1
- package/src/hooks/usePolicy.ts +13 -9
- package/src/hooks/useSensor.ts +1 -1
- package/src/hooks/useTrajectoryPlayer.ts +4 -4
- package/src/hooks/useTrajectoryRecorder.ts +1 -1
- package/src/index.ts +35 -0
- package/src/spark.tsx +138 -4
- package/src/types.ts +128 -21
- 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 {
|
|
3
|
+
import { n as SplatEnvironmentProps } from './types-izZlUweI.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-33CV6HSV.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,10 +322,12 @@ 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[];
|
|
326
|
-
onReset?: (
|
|
330
|
+
onReset?: (input: ResetCallbackInput) => void;
|
|
327
331
|
}
|
|
328
332
|
interface SceneConfig {
|
|
329
333
|
/** Base URL for fetching model files. The loader fetches `src + sceneFile` and follows dependencies. */
|
|
@@ -332,10 +336,18 @@ 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[];
|
|
338
|
-
onReset?: (
|
|
350
|
+
onReset?: (input: ResetCallbackInput) => void;
|
|
339
351
|
}
|
|
340
352
|
type ResourceSelector<TInfo, TName extends string = string> = TName | readonly TName[] | RegExp | ((info: TInfo) => boolean);
|
|
341
353
|
interface IkConfig {
|
|
@@ -368,7 +380,7 @@ interface IkContextValue {
|
|
|
368
380
|
setIkEnabled: (enabled: boolean) => void;
|
|
369
381
|
moveTarget: (pos: THREE.Vector3, duration?: number) => void;
|
|
370
382
|
syncTargetToSite: () => void;
|
|
371
|
-
solveIK: (
|
|
383
|
+
solveIK: (input: IkSolveInput) => number[] | null;
|
|
372
384
|
getGizmoStats: () => {
|
|
373
385
|
pos: THREE.Vector3;
|
|
374
386
|
rot: THREE.Euler;
|
|
@@ -386,14 +398,38 @@ interface PhysicsConfig {
|
|
|
386
398
|
paused?: boolean;
|
|
387
399
|
speed?: number;
|
|
388
400
|
}
|
|
389
|
-
type IKSolveFn = (
|
|
401
|
+
type IKSolveFn = (input: IkSolveInput) => number[] | null;
|
|
402
|
+
interface IkSolveInput {
|
|
403
|
+
position: THREE.Vector3;
|
|
404
|
+
quaternion: THREE.Quaternion;
|
|
405
|
+
currentQ: number[];
|
|
406
|
+
context?: IKSolveContext;
|
|
407
|
+
}
|
|
390
408
|
interface IKSolveContext {
|
|
391
409
|
model: MujocoModel;
|
|
392
410
|
data: MujocoData;
|
|
393
411
|
siteId: number;
|
|
394
412
|
controlGroup: ControlGroupInfo;
|
|
395
413
|
}
|
|
396
|
-
|
|
414
|
+
interface PhysicsStepInput {
|
|
415
|
+
model: MujocoModel;
|
|
416
|
+
data: MujocoData;
|
|
417
|
+
}
|
|
418
|
+
interface ResetCallbackInput extends PhysicsStepInput {
|
|
419
|
+
}
|
|
420
|
+
interface ReadyCallbackInput {
|
|
421
|
+
api: MujocoSimAPI;
|
|
422
|
+
}
|
|
423
|
+
interface StepCallbackInput {
|
|
424
|
+
time: number;
|
|
425
|
+
model: MujocoModel;
|
|
426
|
+
data: MujocoData;
|
|
427
|
+
}
|
|
428
|
+
interface SelectionCallbackInput {
|
|
429
|
+
bodyId: number;
|
|
430
|
+
name: string;
|
|
431
|
+
}
|
|
432
|
+
type PhysicsStepCallback = (input: PhysicsStepInput) => void;
|
|
397
433
|
interface StateSnapshot {
|
|
398
434
|
time: number;
|
|
399
435
|
qpos: Float64Array;
|
|
@@ -524,10 +560,24 @@ interface KeyboardTeleopConfig {
|
|
|
524
560
|
bindings: Record<string, KeyBinding>;
|
|
525
561
|
enabled?: boolean;
|
|
526
562
|
}
|
|
563
|
+
type PolicyVector = Float32Array | Float64Array | number[];
|
|
564
|
+
interface PolicyObservationInput {
|
|
565
|
+
model: MujocoModel;
|
|
566
|
+
data: MujocoData;
|
|
567
|
+
}
|
|
568
|
+
interface PolicyInferenceInput extends PolicyObservationInput {
|
|
569
|
+
observation: PolicyVector;
|
|
570
|
+
}
|
|
571
|
+
interface PolicyActionInput extends PolicyInferenceInput {
|
|
572
|
+
action: PolicyVector;
|
|
573
|
+
}
|
|
527
574
|
interface PolicyConfig {
|
|
528
575
|
frequency: number;
|
|
529
|
-
|
|
530
|
-
|
|
576
|
+
enabled?: boolean;
|
|
577
|
+
onObservation: (input: PolicyObservationInput) => PolicyVector;
|
|
578
|
+
/** Run policy inference. Omit to pass observations directly to `onAction` for custom inline controllers. */
|
|
579
|
+
infer?: (input: PolicyInferenceInput) => PolicyVector;
|
|
580
|
+
onAction: (input: PolicyActionInput) => void;
|
|
531
581
|
}
|
|
532
582
|
type ObservationOutput = 'float32' | 'float64';
|
|
533
583
|
interface ObservationConfig {
|
|
@@ -580,7 +630,11 @@ interface IkGizmoProps {
|
|
|
580
630
|
controller: IkContextValue;
|
|
581
631
|
siteName?: string;
|
|
582
632
|
scale?: number;
|
|
583
|
-
onDrag?: (
|
|
633
|
+
onDrag?: (input: IkGizmoDragInput) => void;
|
|
634
|
+
}
|
|
635
|
+
interface IkGizmoDragInput {
|
|
636
|
+
position: THREE.Vector3;
|
|
637
|
+
quaternion: THREE.Quaternion;
|
|
584
638
|
}
|
|
585
639
|
interface DragInteractionProps {
|
|
586
640
|
stiffness?: number;
|
|
@@ -600,6 +654,12 @@ interface ScenarioCameraConfig {
|
|
|
600
654
|
noise?: number;
|
|
601
655
|
blur?: number;
|
|
602
656
|
}
|
|
657
|
+
interface ScenarioMaterialConfig {
|
|
658
|
+
randomizeObjectColors?: boolean;
|
|
659
|
+
randomizeTableMaterial?: boolean;
|
|
660
|
+
roughness?: number;
|
|
661
|
+
metalness?: number;
|
|
662
|
+
}
|
|
603
663
|
interface SplatAssetConfig {
|
|
604
664
|
src: string;
|
|
605
665
|
/** Common browser-friendly splat format. Renderer-specific loaders may accept more. */
|
|
@@ -613,7 +673,7 @@ interface SplatScenarioConfig {
|
|
|
613
673
|
format?: SplatFormat;
|
|
614
674
|
src?: string;
|
|
615
675
|
requiresCollisionProxy?: boolean;
|
|
616
|
-
collisionProxy?: SplatCollisionProxyConfig;
|
|
676
|
+
collisionProxy?: SplatCollisionProxyConfig | null;
|
|
617
677
|
}
|
|
618
678
|
interface SplatCollisionProxyConfig {
|
|
619
679
|
/** MJCF/XML file or artifact path that provides physics collision for the visual splat. */
|
|
@@ -638,6 +698,8 @@ interface PairedSplatEnvironmentConfig {
|
|
|
638
698
|
}
|
|
639
699
|
interface SplatEnvironmentMetadataInput {
|
|
640
700
|
environment?: PairedSplatEnvironmentConfig;
|
|
701
|
+
scenario?: VisualScenarioConfig;
|
|
702
|
+
renderer?: SplatRendererKind;
|
|
641
703
|
src?: string;
|
|
642
704
|
format?: SplatFormat;
|
|
643
705
|
collisionProxy?: SplatCollisionProxyConfig;
|
|
@@ -648,6 +710,7 @@ interface SplatEnvironmentMetadata {
|
|
|
648
710
|
collisionProxy?: SplatCollisionProxyConfig;
|
|
649
711
|
userData: Record<string, unknown>;
|
|
650
712
|
}
|
|
713
|
+
type SplatSceneInput = PairedSplatEnvironmentConfig | VisualScenarioConfig | undefined | null;
|
|
651
714
|
interface VisualScenarioConfig {
|
|
652
715
|
id?: string;
|
|
653
716
|
label?: string;
|
|
@@ -655,7 +718,8 @@ interface VisualScenarioConfig {
|
|
|
655
718
|
lighting?: ScenarioLightingPreset;
|
|
656
719
|
environment?: string;
|
|
657
720
|
camera?: ScenarioCameraConfig;
|
|
658
|
-
|
|
721
|
+
materials?: ScenarioMaterialConfig;
|
|
722
|
+
splat?: SplatScenarioConfig | null;
|
|
659
723
|
}
|
|
660
724
|
interface ScenarioLightingProps {
|
|
661
725
|
preset?: ScenarioLightingPreset;
|
|
@@ -664,12 +728,30 @@ interface ScenarioLightingProps {
|
|
|
664
728
|
}
|
|
665
729
|
interface SplatEnvironmentProps extends Omit<ThreeElements['group'], 'ref'> {
|
|
666
730
|
environment?: PairedSplatEnvironmentConfig;
|
|
731
|
+
scenario?: VisualScenarioConfig;
|
|
732
|
+
renderer?: SplatRendererKind;
|
|
667
733
|
src?: string;
|
|
668
734
|
format?: SplatFormat;
|
|
669
735
|
collisionProxy?: ReactNode;
|
|
670
736
|
collisionProxyMetadata?: SplatCollisionProxyConfig;
|
|
671
737
|
showPlaceholder?: boolean;
|
|
672
738
|
}
|
|
739
|
+
interface VisualScenarioEffectsProps {
|
|
740
|
+
scenario?: VisualScenarioConfig;
|
|
741
|
+
enabled?: boolean;
|
|
742
|
+
applyBackground?: boolean;
|
|
743
|
+
applyFog?: boolean;
|
|
744
|
+
applyRenderer?: boolean;
|
|
745
|
+
applyMaterials?: boolean;
|
|
746
|
+
background?: THREE.ColorRepresentation;
|
|
747
|
+
fogNear?: number;
|
|
748
|
+
fogFar?: number;
|
|
749
|
+
materialFilter?: (input: VisualScenarioMaterialFilterInput) => boolean;
|
|
750
|
+
}
|
|
751
|
+
interface VisualScenarioMaterialFilterInput {
|
|
752
|
+
object: THREE.Object3D;
|
|
753
|
+
material: THREE.Material;
|
|
754
|
+
}
|
|
673
755
|
type TrajectoryInput = TrajectoryFrame[] | number[][];
|
|
674
756
|
interface TrajectoryPlayerProps {
|
|
675
757
|
trajectory: TrajectoryInput;
|
|
@@ -678,9 +760,16 @@ interface TrajectoryPlayerProps {
|
|
|
678
760
|
loop?: boolean;
|
|
679
761
|
playing?: boolean;
|
|
680
762
|
mode?: 'kinematic' | 'physics';
|
|
681
|
-
onFrame?: (
|
|
763
|
+
onFrame?: (input: TrajectoryFrameCallbackInput) => void;
|
|
682
764
|
onComplete?: () => void;
|
|
683
|
-
onStateChange?: (
|
|
765
|
+
onStateChange?: (input: TrajectoryStateChangeInput) => void;
|
|
766
|
+
}
|
|
767
|
+
interface TrajectoryFrameCallbackInput {
|
|
768
|
+
frameIndex: number;
|
|
769
|
+
frame: TrajectoryFrame | number[] | undefined;
|
|
770
|
+
}
|
|
771
|
+
interface TrajectoryStateChangeInput {
|
|
772
|
+
state: PlaybackState;
|
|
684
773
|
}
|
|
685
774
|
interface ContactListenerProps {
|
|
686
775
|
body: Bodies;
|
|
@@ -699,6 +788,8 @@ interface BodyProps {
|
|
|
699
788
|
solref?: string;
|
|
700
789
|
solimp?: string;
|
|
701
790
|
condim?: number;
|
|
791
|
+
/** MuJoCo geom group. Group 3 is conventionally used for collision-only helper geoms. */
|
|
792
|
+
group?: number;
|
|
702
793
|
children?: ReactNode;
|
|
703
794
|
}
|
|
704
795
|
interface MujocoSimAPI {
|
|
@@ -760,10 +851,12 @@ interface MujocoSimAPI {
|
|
|
760
851
|
}
|
|
761
852
|
type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
|
|
762
853
|
config: SceneConfig;
|
|
763
|
-
|
|
854
|
+
/** R3F content rendered while the MuJoCo WASM module is still loading. */
|
|
855
|
+
loadingFallback?: ReactNode;
|
|
856
|
+
onReady?: (input: ReadyCallbackInput) => void;
|
|
764
857
|
onError?: (error: Error) => void;
|
|
765
|
-
onStep?: (
|
|
766
|
-
onSelection?: (
|
|
858
|
+
onStep?: (input: StepCallbackInput) => void;
|
|
859
|
+
onSelection?: (input: SelectionCallbackInput) => void;
|
|
767
860
|
gravity?: [number, number, number];
|
|
768
861
|
timestep?: number;
|
|
769
862
|
substeps?: number;
|
|
@@ -814,4 +907,4 @@ interface JointStateResult {
|
|
|
814
907
|
velocity: React__default.RefObject<number | Float64Array>;
|
|
815
908
|
}
|
|
816
909
|
|
|
817
|
-
export { type
|
|
910
|
+
export { type PolicyConfig as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type ActuatorInfo as E, type Sites as F, type GeomInfo as G, type SitePositionResult as H, type IkConfig as I, type Sensors as J, type SensorHandle as K, type SensorInfo as L, type MujocoContextValue as M, type Joints as N, type ObservationConfig as O, type PhysicsStepCallback as P, type JointStateResult as Q, type ReadyCallbackInput as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type Bodies as U, type VisualScenarioEffectsProps as V, type BodyStateResult as W, type Actuators as X, type CtrlHandle as Y, type ContactInfo as Z, type KeyboardTeleopConfig as _, type MujocoCanvasProps as a, type PolicyVector as a0, type ObservationHandle as a1, type TrajectoryInput as a2, type TrajectoryStateChangeInput as a3, type PlaybackState as a4, type TrajectoryFrame as a5, type BodyInfo as a6, type ControlJointInfo as a7, type Geoms as a8, type IKSolveFn as a9, RobotResources as aA, RobotSensors as aB, RobotSites as aC, type Robots as aD, type ScenarioCameraConfig as aE, type ScenarioMaterialConfig as aF, type SceneMarker as aG, type SceneObject as aH, type SensorResult as aI, type SiteInfo as aJ, type SplatAssetConfig as aK, type SplatScenarioConfig as aL, type StateSnapshot as aM, type TrajectoryData as aN, type TrajectoryFrameCallbackInput as aO, type VisualScenarioMaterialFilterInput as aP, type XmlPatch as aQ, getContact as aR, registerRobotResources as aS, type IkGizmoDragInput as aa, type IkSolveInput as ab, type JointInfo as ac, type KeyBinding as ad, type Keyframes as ae, type ModelOptions as af, type MujocoContact as ag, type MujocoContactArray as ah, type ObservationLayoutItem as ai, type ObservationOutput as aj, type PhysicsConfig as ak, type PhysicsStepInput as al, type PolicyActionInput as am, type PolicyInferenceInput as an, type PolicyObservationInput as ao, type RayHit as ap, type Register as aq, type RegisteredRobotMap as ar, type ResetCallbackInput as as, type ResourceSelector as at, RobotActuators as au, RobotBodies as av, RobotGeoms as aw, RobotJoints as ax, RobotKeyframes as ay, type RobotResource as az, type MujocoSimAPI as b, type StepCallbackInput as c, type SelectionCallbackInput as d, type MujocoModule as e, type MujocoModel as f, type MujocoData as g, type ControlGroupSelector as h, type ObservationResult as i, type IkContextValue as j, type IkGizmoProps as k, type SceneLightsProps as l, type ScenarioLightingProps as m, type SplatEnvironmentProps as n, type VisualScenarioConfig as o, type SplatRendererKind as p, type PairedSplatEnvironmentConfig as q, type SplatFormat as r, type SplatCollisionProxyConfig as s, type SplatCollisionPrimitive as t, type ScenarioLightingPreset as u, type SplatEnvironmentMetadataInput as v, type SplatEnvironmentMetadata as w, type SplatSceneInput as x, type DebugProps as y, type ContactListenerProps 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(() => {
|
|
@@ -168,7 +168,7 @@ export function DragInteraction({
|
|
|
168
168
|
}, [gl, camera, scene, controls, mjDataRef]);
|
|
169
169
|
|
|
170
170
|
// Apply spring force each physics frame
|
|
171
|
-
useBeforePhysicsStep((model, data) => {
|
|
171
|
+
useBeforePhysicsStep(({ model, data }) => {
|
|
172
172
|
if (!draggingRef.current || bodyIdRef.current <= 0) return;
|
|
173
173
|
|
|
174
174
|
const bid = bodyIdRef.current;
|
|
@@ -26,7 +26,7 @@ const _scale = new THREE.Vector3(1, 1, 1);
|
|
|
26
26
|
* - `controller` — IkContextValue from `useIkController()`.
|
|
27
27
|
* - `siteName` — MuJoCo site to track. Defaults to the controller's configured site.
|
|
28
28
|
* - `scale` — Gizmo handle scale. Default: 0.18.
|
|
29
|
-
* - `onDrag` — Custom drag callback `(
|
|
29
|
+
* - `onDrag` — Custom drag callback `({ position, quaternion }) => void`.
|
|
30
30
|
* When omitted, dragging enables IK and writes to the IK target.
|
|
31
31
|
* When provided, the consumer handles what happens during drag.
|
|
32
32
|
*/
|
|
@@ -112,7 +112,7 @@ export function IkGizmo({ controller, siteName, scale = 0.18, onDrag }: IkGizmoP
|
|
|
112
112
|
world.decompose(_pos, _quat, _scale);
|
|
113
113
|
if (onDrag) {
|
|
114
114
|
// Custom: consumer handles the drag
|
|
115
|
-
onDrag(_pos.clone(), _quat.clone());
|
|
115
|
+
onDrag({ position: _pos.clone(), quaternion: _quat.clone() });
|
|
116
116
|
} else {
|
|
117
117
|
// Default: write to IK target
|
|
118
118
|
const target = ikTargetRef.current;
|
|
@@ -139,7 +139,7 @@ export function SceneRenderer(props: Omit<ThreeElements['group'], 'ref'>) {
|
|
|
139
139
|
const model = mjModelRef.current;
|
|
140
140
|
if (model && bodyID < model.nbody && onSelectionRef.current) {
|
|
141
141
|
const name = getName(model, model.name_bodyadr[bodyID]);
|
|
142
|
-
onSelectionRef.current(bodyID, name);
|
|
142
|
+
onSelectionRef.current({ bodyId: bodyID, name });
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
}}
|
|
@@ -54,7 +54,10 @@ export function TrajectoryPlayer({
|
|
|
54
54
|
const currentFrame = player.frame;
|
|
55
55
|
if (currentFrame !== lastReportedFrameRef.current && player.playing) {
|
|
56
56
|
lastReportedFrameRef.current = currentFrame;
|
|
57
|
-
onFrameRef.current(
|
|
57
|
+
onFrameRef.current({
|
|
58
|
+
frameIndex: currentFrame,
|
|
59
|
+
frame: trajectory[currentFrame],
|
|
60
|
+
});
|
|
58
61
|
}
|
|
59
62
|
});
|
|
60
63
|
|