mujoco-react 8.9.2 → 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 +82 -1
- package/dist/chunk-SEWQULWO.js +400 -0
- package/dist/chunk-SEWQULWO.js.map +1 -0
- package/dist/index.d.ts +114 -744
- package/dist/index.js +329 -35
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +53 -0
- package/dist/spark.js +235 -0
- package/dist/spark.js.map +1 -0
- package/dist/types-BmneHLBM.d.ts +871 -0
- package/dist/vite.d.ts +9 -0
- package/dist/vite.js +4 -0
- package/dist/vite.js.map +1 -1
- package/package.json +15 -2
- package/src/components/Body.tsx +3 -1
- package/src/components/VisualScenario.tsx +566 -0
- 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/hooks/useSceneLights.ts +49 -18
- package/src/index.ts +48 -0
- package/src/spark.tsx +336 -0
- package/src/types.ts +159 -3
- package/src/vite.ts +8 -0
package/src/spark.tsx
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useThree } from '@react-three/fiber';
|
|
7
|
+
import {
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useMemo,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from 'react';
|
|
14
|
+
import * as THREE from 'three';
|
|
15
|
+
import {
|
|
16
|
+
SplatEnvironment,
|
|
17
|
+
useSplatEnvironment,
|
|
18
|
+
} from './components/VisualScenario';
|
|
19
|
+
import type {
|
|
20
|
+
SplatEnvironmentProps,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
type SparkModule = typeof import('@sparkjsdev/spark');
|
|
24
|
+
type SparkRendererInstance = InstanceType<SparkModule['SparkRenderer']>;
|
|
25
|
+
type SparkSplatMeshInstance = InstanceType<SparkModule['SplatMesh']>;
|
|
26
|
+
type SparkDisposable = {
|
|
27
|
+
dispose?: () => unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type SparkSplatStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
31
|
+
|
|
32
|
+
export interface SparkSplatLifecycle {
|
|
33
|
+
status: SparkSplatStatus;
|
|
34
|
+
error: Error | null;
|
|
35
|
+
isLoading: boolean;
|
|
36
|
+
isReady: boolean;
|
|
37
|
+
isError: boolean;
|
|
38
|
+
props: Pick<SparkSplatEnvironmentProps, 'onStatusChange' | 'onError'>;
|
|
39
|
+
reset: () => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SparkSplatEnvironmentProps extends SplatEnvironmentProps {
|
|
43
|
+
/** Enable Spark LoD handling for large splat assets. Default: true. */
|
|
44
|
+
lod?: boolean | 'quality';
|
|
45
|
+
/**
|
|
46
|
+
* Hide meshes whose names include floor, ground, or plane while the splat is
|
|
47
|
+
* active. This mirrors the common hybrid-rendering setup where MJCF keeps
|
|
48
|
+
* collision geometry but the splat owns the visual environment.
|
|
49
|
+
*/
|
|
50
|
+
hideGroundMeshes?: boolean;
|
|
51
|
+
onStatusChange?: (status: SparkSplatStatus) => void;
|
|
52
|
+
onLoad?: (mesh: SparkSplatMeshInstance) => void;
|
|
53
|
+
onError?: (error: Error) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Tracks Spark 3DGS loading state for UI that wraps `SparkSplatEnvironment`.
|
|
58
|
+
*
|
|
59
|
+
* Use the returned `props` with `<SparkSplatEnvironment {...lifecycle.props} />`
|
|
60
|
+
* to avoid repeating status/error state in app code.
|
|
61
|
+
*/
|
|
62
|
+
export function useSparkSplatLifecycle({
|
|
63
|
+
enabled = true,
|
|
64
|
+
initialStatus,
|
|
65
|
+
onError,
|
|
66
|
+
onStatusChange,
|
|
67
|
+
}: {
|
|
68
|
+
enabled?: boolean;
|
|
69
|
+
initialStatus?: SparkSplatStatus;
|
|
70
|
+
onError?: (error: Error) => void;
|
|
71
|
+
onStatusChange?: (status: SparkSplatStatus) => void;
|
|
72
|
+
} = {}): SparkSplatLifecycle {
|
|
73
|
+
const [status, setStatus] = useState<SparkSplatStatus>(
|
|
74
|
+
initialStatus ?? (enabled ? 'loading' : 'idle')
|
|
75
|
+
);
|
|
76
|
+
const [error, setError] = useState<Error | null>(null);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setStatus(enabled ? initialStatus ?? 'loading' : 'idle');
|
|
80
|
+
setError(null);
|
|
81
|
+
}, [enabled, initialStatus]);
|
|
82
|
+
|
|
83
|
+
const handleStatusChange = useCallback(
|
|
84
|
+
(nextStatus: SparkSplatStatus) => {
|
|
85
|
+
setStatus(nextStatus);
|
|
86
|
+
if (nextStatus !== 'error') {
|
|
87
|
+
setError(null);
|
|
88
|
+
}
|
|
89
|
+
onStatusChange?.(nextStatus);
|
|
90
|
+
},
|
|
91
|
+
[onStatusChange]
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const handleError = useCallback(
|
|
95
|
+
(nextError: Error) => {
|
|
96
|
+
setError(nextError);
|
|
97
|
+
setStatus('error');
|
|
98
|
+
onError?.(nextError);
|
|
99
|
+
},
|
|
100
|
+
[onError]
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const reset = useCallback(() => {
|
|
104
|
+
setStatus(enabled ? initialStatus ?? 'loading' : 'idle');
|
|
105
|
+
setError(null);
|
|
106
|
+
}, [enabled, initialStatus]);
|
|
107
|
+
|
|
108
|
+
return useMemo(
|
|
109
|
+
() => ({
|
|
110
|
+
status,
|
|
111
|
+
error,
|
|
112
|
+
isLoading: status === 'loading',
|
|
113
|
+
isReady: status === 'ready',
|
|
114
|
+
isError: status === 'error',
|
|
115
|
+
props: {
|
|
116
|
+
onStatusChange: handleStatusChange,
|
|
117
|
+
onError: handleError,
|
|
118
|
+
},
|
|
119
|
+
reset,
|
|
120
|
+
}),
|
|
121
|
+
[error, handleError, handleStatusChange, reset, status]
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Optional SparkJS-backed Gaussian splat renderer for React Three Fiber scenes.
|
|
127
|
+
*
|
|
128
|
+
* Import from `mujoco-react/spark` and install `@sparkjsdev/spark` in the app
|
|
129
|
+
* that uses it. The core `mujoco-react` entrypoint does not depend on Spark.
|
|
130
|
+
*/
|
|
131
|
+
export function SparkSplatEnvironment({
|
|
132
|
+
environment,
|
|
133
|
+
scenario,
|
|
134
|
+
renderer = 'spark',
|
|
135
|
+
src,
|
|
136
|
+
format,
|
|
137
|
+
collisionProxy,
|
|
138
|
+
collisionProxyMetadata,
|
|
139
|
+
showPlaceholder,
|
|
140
|
+
children,
|
|
141
|
+
lod = true,
|
|
142
|
+
hideGroundMeshes = false,
|
|
143
|
+
onStatusChange,
|
|
144
|
+
onLoad,
|
|
145
|
+
onError,
|
|
146
|
+
...groupProps
|
|
147
|
+
}: SparkSplatEnvironmentProps) {
|
|
148
|
+
const groupRef = useRef<THREE.Group>(null);
|
|
149
|
+
const sparkRef = useRef<SparkRendererInstance | null>(null);
|
|
150
|
+
const meshRef = useRef<SparkSplatMeshInstance | null>(null);
|
|
151
|
+
const hiddenMeshesRef = useRef<THREE.Mesh[]>([]);
|
|
152
|
+
const onStatusChangeRef = useRef(onStatusChange);
|
|
153
|
+
const onLoadRef = useRef(onLoad);
|
|
154
|
+
const onErrorRef = useRef(onError);
|
|
155
|
+
const [status, setStatus] = useState<SparkSplatStatus>('idle');
|
|
156
|
+
const { gl, invalidate } = useThree();
|
|
157
|
+
const metadata = useSplatEnvironment({
|
|
158
|
+
environment,
|
|
159
|
+
scenario,
|
|
160
|
+
renderer,
|
|
161
|
+
src,
|
|
162
|
+
format,
|
|
163
|
+
collisionProxy: collisionProxyMetadata,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
onStatusChangeRef.current = onStatusChange;
|
|
168
|
+
}, [onStatusChange]);
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
onLoadRef.current = onLoad;
|
|
172
|
+
}, [onLoad]);
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
onErrorRef.current = onError;
|
|
176
|
+
}, [onError]);
|
|
177
|
+
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
let disposed = false;
|
|
180
|
+
|
|
181
|
+
function setLifecycleStatus(nextStatus: SparkSplatStatus) {
|
|
182
|
+
setStatus(nextStatus);
|
|
183
|
+
onStatusChangeRef.current?.(nextStatus);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function restoreHiddenMeshes() {
|
|
187
|
+
for (const mesh of hiddenMeshesRef.current) {
|
|
188
|
+
mesh.visible = true;
|
|
189
|
+
}
|
|
190
|
+
hiddenMeshesRef.current = [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function loadSplat() {
|
|
194
|
+
if (!metadata.src) {
|
|
195
|
+
setLifecycleStatus('idle');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (metadata.format !== 'spz') {
|
|
200
|
+
const unsupportedFormatError = new Error(
|
|
201
|
+
`SparkSplatEnvironment only supports .spz assets; received "${metadata.format}".`
|
|
202
|
+
);
|
|
203
|
+
setLifecycleStatus('error');
|
|
204
|
+
onErrorRef.current?.(unsupportedFormatError);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
setLifecycleStatus('loading');
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const sparkModule = await import('@sparkjsdev/spark');
|
|
212
|
+
if (disposed || !groupRef.current) return;
|
|
213
|
+
|
|
214
|
+
const spark = new sparkModule.SparkRenderer({
|
|
215
|
+
renderer: gl,
|
|
216
|
+
onDirty: invalidate,
|
|
217
|
+
});
|
|
218
|
+
const mesh = new sparkModule.SplatMesh({
|
|
219
|
+
url: metadata.src,
|
|
220
|
+
lod,
|
|
221
|
+
});
|
|
222
|
+
mesh.name = 'GaussianSplatMesh';
|
|
223
|
+
|
|
224
|
+
groupRef.current.add(spark);
|
|
225
|
+
groupRef.current.add(mesh);
|
|
226
|
+
sparkRef.current = spark;
|
|
227
|
+
meshRef.current = mesh;
|
|
228
|
+
|
|
229
|
+
if (hideGroundMeshes && groupRef.current.parent) {
|
|
230
|
+
groupRef.current.parent.traverse((object) => {
|
|
231
|
+
if (
|
|
232
|
+
!(object instanceof THREE.Mesh) ||
|
|
233
|
+
object === (mesh as unknown as THREE.Object3D)
|
|
234
|
+
) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const name = object.name.toLowerCase();
|
|
238
|
+
if (
|
|
239
|
+
name.includes('floor') ||
|
|
240
|
+
name.includes('ground') ||
|
|
241
|
+
name.includes('plane')
|
|
242
|
+
) {
|
|
243
|
+
object.visible = false;
|
|
244
|
+
hiddenMeshesRef.current.push(object);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await mesh.initialized;
|
|
250
|
+
if (disposed) return;
|
|
251
|
+
setLifecycleStatus('ready');
|
|
252
|
+
onLoadRef.current?.(mesh);
|
|
253
|
+
invalidate();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const normalizedError =
|
|
256
|
+
error instanceof Error ? error : new Error(String(error));
|
|
257
|
+
setLifecycleStatus('error');
|
|
258
|
+
onErrorRef.current?.(normalizedError);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
void loadSplat();
|
|
263
|
+
|
|
264
|
+
return () => {
|
|
265
|
+
disposed = true;
|
|
266
|
+
restoreHiddenMeshes();
|
|
267
|
+
|
|
268
|
+
if (meshRef.current) {
|
|
269
|
+
groupRef.current?.remove(meshRef.current);
|
|
270
|
+
safelyDisposeSparkResource(meshRef.current);
|
|
271
|
+
meshRef.current = null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (sparkRef.current) {
|
|
275
|
+
groupRef.current?.remove(sparkRef.current);
|
|
276
|
+
safelyDisposeSparkResource(sparkRef.current);
|
|
277
|
+
sparkRef.current = null;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}, [
|
|
281
|
+
gl,
|
|
282
|
+
hideGroundMeshes,
|
|
283
|
+
invalidate,
|
|
284
|
+
lod,
|
|
285
|
+
metadata.format,
|
|
286
|
+
metadata.src,
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<SplatEnvironment
|
|
291
|
+
{...groupProps}
|
|
292
|
+
environment={environment}
|
|
293
|
+
scenario={scenario}
|
|
294
|
+
renderer={renderer}
|
|
295
|
+
src={metadata.src}
|
|
296
|
+
format={metadata.format}
|
|
297
|
+
collisionProxyMetadata={metadata.collisionProxy}
|
|
298
|
+
collisionProxy={collisionProxy}
|
|
299
|
+
showPlaceholder={showPlaceholder ?? status !== 'ready'}
|
|
300
|
+
>
|
|
301
|
+
<group ref={groupRef} />
|
|
302
|
+
{children}
|
|
303
|
+
</SplatEnvironment>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function safelyDisposeSparkResource(resource: SparkDisposable) {
|
|
308
|
+
try {
|
|
309
|
+
const result = resource.dispose?.();
|
|
310
|
+
if (isPromiseLike(result)) {
|
|
311
|
+
void Promise.resolve(result).catch(handleSparkDisposeError);
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
handleSparkDisposeError(error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
319
|
+
return (
|
|
320
|
+
typeof value === 'object' &&
|
|
321
|
+
value !== null &&
|
|
322
|
+
'then' in value &&
|
|
323
|
+
typeof (value as { then?: unknown }).then === 'function'
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function handleSparkDisposeError(error: unknown) {
|
|
328
|
+
if (
|
|
329
|
+
error instanceof Error &&
|
|
330
|
+
error.message.toLowerCase().includes('worker terminate')
|
|
331
|
+
) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.warn('[mujoco-react] Spark resource disposal failed.', error);
|
|
336
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type React from 'react';
|
|
7
7
|
import type { ReactNode } from 'react';
|
|
8
|
-
import type { CanvasProps } from '@react-three/fiber';
|
|
8
|
+
import type { CanvasProps, ThreeElements } from '@react-three/fiber';
|
|
9
9
|
import * as THREE from 'three';
|
|
10
10
|
|
|
11
11
|
// ---- Register (type-safe named resources) ----
|
|
@@ -429,6 +429,8 @@ export interface SceneObject {
|
|
|
429
429
|
solref?: string;
|
|
430
430
|
solimp?: string;
|
|
431
431
|
condim?: number;
|
|
432
|
+
/** MuJoCo geom group. Group 3 is conventionally used for collision-only helper geoms. */
|
|
433
|
+
group?: number;
|
|
432
434
|
}
|
|
433
435
|
|
|
434
436
|
export interface XmlPatch {
|
|
@@ -443,6 +445,8 @@ export type LocalMujocoFile = File;
|
|
|
443
445
|
export interface LoadFromFilesOptions {
|
|
444
446
|
/** Entry MJCF/URDF file. Inferred from scene.xml, model.xml, robot.xml, or the first XML/URDF file when omitted. */
|
|
445
447
|
sceneFile?: string;
|
|
448
|
+
/** Additional MJCF environment XML files merged into the entry scene before MuJoCo compilation. */
|
|
449
|
+
environmentFiles?: string[];
|
|
446
450
|
homeJoints?: number[];
|
|
447
451
|
xmlPatches?: XmlPatch[];
|
|
448
452
|
sceneObjects?: SceneObject[];
|
|
@@ -456,6 +460,14 @@ export interface SceneConfig {
|
|
|
456
460
|
sceneFile: string;
|
|
457
461
|
/** Browser-selected files for local MJCF/URDF loading. Preserves webkitRelativePath when available. */
|
|
458
462
|
files?: readonly LocalMujocoFile[];
|
|
463
|
+
/**
|
|
464
|
+
* Additional MJCF environment XML files merged into the entry scene before compilation.
|
|
465
|
+
*
|
|
466
|
+
* Use this for static collision/physics layers such as a Gaussian-splat
|
|
467
|
+
* environment's proxy `scene.xml`; render the splat itself as a separate
|
|
468
|
+
* visual layer.
|
|
469
|
+
*/
|
|
470
|
+
environmentFiles?: string[];
|
|
459
471
|
sceneObjects?: SceneObject[];
|
|
460
472
|
homeJoints?: number[];
|
|
461
473
|
xmlPatches?: XmlPatch[];
|
|
@@ -709,10 +721,28 @@ export interface KeyboardTeleopConfig {
|
|
|
709
721
|
|
|
710
722
|
// ---- Policy (spec 10.1) ----
|
|
711
723
|
|
|
724
|
+
export type PolicyVector = Float32Array | Float64Array | number[];
|
|
725
|
+
|
|
726
|
+
export interface PolicyObservationInput {
|
|
727
|
+
model: MujocoModel;
|
|
728
|
+
data: MujocoData;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export interface PolicyInferenceInput extends PolicyObservationInput {
|
|
732
|
+
observation: PolicyVector;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
export interface PolicyActionInput extends PolicyInferenceInput {
|
|
736
|
+
action: PolicyVector;
|
|
737
|
+
}
|
|
738
|
+
|
|
712
739
|
export interface PolicyConfig {
|
|
713
740
|
frequency: number;
|
|
714
|
-
|
|
715
|
-
|
|
741
|
+
enabled?: boolean;
|
|
742
|
+
onObservation: (input: PolicyObservationInput) => PolicyVector;
|
|
743
|
+
/** Run policy inference. Omit to pass observations directly to `onAction` for custom inline controllers. */
|
|
744
|
+
infer?: (input: PolicyInferenceInput) => PolicyVector;
|
|
745
|
+
onAction: (input: PolicyActionInput) => void;
|
|
716
746
|
}
|
|
717
747
|
|
|
718
748
|
// ---- Observation Builder ----
|
|
@@ -791,6 +821,128 @@ export interface SceneLightsProps {
|
|
|
791
821
|
intensity?: number;
|
|
792
822
|
}
|
|
793
823
|
|
|
824
|
+
// ---- Visual scenarios / 3DGS composition ----
|
|
825
|
+
|
|
826
|
+
export type ScenarioLightingPreset = 'studio' | 'warehouse' | 'low-light' | 'splat';
|
|
827
|
+
export type SplatFormat = 'spz' | 'ply' | 'splat';
|
|
828
|
+
export type SplatRendererKind = 'spark' | 'custom';
|
|
829
|
+
export type SplatCollisionPrimitive = 'plane' | 'box' | 'sphere' | 'capsule' | 'mesh';
|
|
830
|
+
|
|
831
|
+
export interface ScenarioCameraConfig {
|
|
832
|
+
jitter?: number;
|
|
833
|
+
exposure?: number;
|
|
834
|
+
noise?: number;
|
|
835
|
+
blur?: number;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export interface ScenarioMaterialConfig {
|
|
839
|
+
randomizeObjectColors?: boolean;
|
|
840
|
+
randomizeTableMaterial?: boolean;
|
|
841
|
+
roughness?: number;
|
|
842
|
+
metalness?: number;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
export interface SplatAssetConfig {
|
|
846
|
+
src: string;
|
|
847
|
+
/** Common browser-friendly splat format. Renderer-specific loaders may accept more. */
|
|
848
|
+
format?: SplatFormat;
|
|
849
|
+
/** Optional renderer hint. The library does not import renderer-specific code. */
|
|
850
|
+
renderer?: SplatRendererKind;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
export interface SplatScenarioConfig {
|
|
854
|
+
enabled: boolean;
|
|
855
|
+
/** Common browser-friendly splat format. Renderer-specific loaders may accept more. */
|
|
856
|
+
format?: SplatFormat;
|
|
857
|
+
src?: string;
|
|
858
|
+
requiresCollisionProxy?: boolean;
|
|
859
|
+
collisionProxy?: SplatCollisionProxyConfig | null;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
export interface SplatCollisionProxyConfig {
|
|
863
|
+
/** MJCF/XML file or artifact path that provides physics collision for the visual splat. */
|
|
864
|
+
xmlPath?: string;
|
|
865
|
+
/** Human-readable status for authoring and validation flows. */
|
|
866
|
+
status?: 'missing' | 'planned' | 'generated' | 'validated';
|
|
867
|
+
/** Primitive proxy shapes expected in the MJCF collision proxy. */
|
|
868
|
+
primitives?: SplatCollisionPrimitive[];
|
|
869
|
+
/** Optional notes that should travel with scene variants and rollout metadata. */
|
|
870
|
+
notes?: string[];
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export interface PairedSplatEnvironmentConfig {
|
|
874
|
+
id: string;
|
|
875
|
+
label: string;
|
|
876
|
+
description?: string;
|
|
877
|
+
/** Visual-only Gaussian splat asset. */
|
|
878
|
+
splat: SplatAssetConfig;
|
|
879
|
+
/** MJCF/XML contact geometry paired with the visual splat. */
|
|
880
|
+
collisionProxy: SplatCollisionProxyConfig & { xmlPath: string };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
export interface SplatEnvironmentMetadataInput {
|
|
884
|
+
environment?: PairedSplatEnvironmentConfig;
|
|
885
|
+
scenario?: VisualScenarioConfig;
|
|
886
|
+
renderer?: SplatRendererKind;
|
|
887
|
+
src?: string;
|
|
888
|
+
format?: SplatFormat;
|
|
889
|
+
collisionProxy?: SplatCollisionProxyConfig;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export interface SplatEnvironmentMetadata {
|
|
893
|
+
src?: string;
|
|
894
|
+
format: SplatFormat;
|
|
895
|
+
collisionProxy?: SplatCollisionProxyConfig;
|
|
896
|
+
userData: Record<string, unknown>;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
export type SplatSceneInput =
|
|
900
|
+
| PairedSplatEnvironmentConfig
|
|
901
|
+
| VisualScenarioConfig
|
|
902
|
+
| undefined
|
|
903
|
+
| null;
|
|
904
|
+
|
|
905
|
+
export interface VisualScenarioConfig {
|
|
906
|
+
id?: string;
|
|
907
|
+
label?: string;
|
|
908
|
+
seed?: number;
|
|
909
|
+
lighting?: ScenarioLightingPreset;
|
|
910
|
+
environment?: string;
|
|
911
|
+
camera?: ScenarioCameraConfig;
|
|
912
|
+
materials?: ScenarioMaterialConfig;
|
|
913
|
+
splat?: SplatScenarioConfig | null;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
export interface ScenarioLightingProps {
|
|
917
|
+
preset?: ScenarioLightingPreset;
|
|
918
|
+
intensity?: number;
|
|
919
|
+
castShadow?: boolean;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
export interface SplatEnvironmentProps extends Omit<ThreeElements['group'], 'ref'> {
|
|
923
|
+
environment?: PairedSplatEnvironmentConfig;
|
|
924
|
+
scenario?: VisualScenarioConfig;
|
|
925
|
+
renderer?: SplatRendererKind;
|
|
926
|
+
src?: string;
|
|
927
|
+
format?: SplatFormat;
|
|
928
|
+
collisionProxy?: ReactNode;
|
|
929
|
+
collisionProxyMetadata?: SplatCollisionProxyConfig;
|
|
930
|
+
showPlaceholder?: boolean;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
export interface VisualScenarioEffectsProps {
|
|
934
|
+
scenario?: VisualScenarioConfig;
|
|
935
|
+
enabled?: boolean;
|
|
936
|
+
applyBackground?: boolean;
|
|
937
|
+
applyFog?: boolean;
|
|
938
|
+
applyRenderer?: boolean;
|
|
939
|
+
applyMaterials?: boolean;
|
|
940
|
+
background?: THREE.ColorRepresentation;
|
|
941
|
+
fogNear?: number;
|
|
942
|
+
fogFar?: number;
|
|
943
|
+
materialFilter?: (object: THREE.Object3D, material: THREE.Material) => boolean;
|
|
944
|
+
}
|
|
945
|
+
|
|
794
946
|
export type TrajectoryInput = TrajectoryFrame[] | number[][];
|
|
795
947
|
|
|
796
948
|
export interface TrajectoryPlayerProps {
|
|
@@ -829,6 +981,8 @@ export interface BodyProps {
|
|
|
829
981
|
solref?: string;
|
|
830
982
|
solimp?: string;
|
|
831
983
|
condim?: number;
|
|
984
|
+
/** MuJoCo geom group. Group 3 is conventionally used for collision-only helper geoms. */
|
|
985
|
+
group?: number;
|
|
832
986
|
children?: ReactNode;
|
|
833
987
|
}
|
|
834
988
|
|
|
@@ -926,6 +1080,8 @@ export interface MujocoSimAPI {
|
|
|
926
1080
|
|
|
927
1081
|
export type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
|
|
928
1082
|
config: SceneConfig;
|
|
1083
|
+
/** R3F content rendered while the MuJoCo WASM module is still loading. */
|
|
1084
|
+
loadingFallback?: ReactNode;
|
|
929
1085
|
onReady?: (api: MujocoSimAPI) => void;
|
|
930
1086
|
onError?: (error: Error) => void;
|
|
931
1087
|
onStep?: (time: number) => void;
|
package/src/vite.ts
CHANGED
|
@@ -86,6 +86,14 @@ export function mujocoReact(options: MujocoReactPluginOptions) {
|
|
|
86
86
|
return {
|
|
87
87
|
name: 'mujoco-react',
|
|
88
88
|
enforce: 'pre' as const,
|
|
89
|
+
config(userConfig: { build?: { chunkSizeWarningLimit?: number } }) {
|
|
90
|
+
// three + drei + MuJoCo WASM glue are inherently large; the large-chunk
|
|
91
|
+
// warning is expected, not a failure. Raise the limit so consumers don't
|
|
92
|
+
// see it. Vite merges plugin config on top of user config, so only set a
|
|
93
|
+
// default when the consumer hasn't specified their own limit.
|
|
94
|
+
if (userConfig.build?.chunkSizeWarningLimit !== undefined) return;
|
|
95
|
+
return { build: { chunkSizeWarningLimit: 4000 } };
|
|
96
|
+
},
|
|
89
97
|
configResolved(config: ViteConfig) {
|
|
90
98
|
root = config.root;
|
|
91
99
|
generatedRegister = path.resolve(root, generatedRegister);
|