mujoco-react 9.2.0 → 9.4.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 +225 -15
- package/dist/{chunk-33CV6HSV.js → chunk-VDSEPZYQ.js} +303 -14
- package/dist/chunk-VDSEPZYQ.js.map +1 -0
- package/dist/index.d.ts +274 -7
- package/dist/index.js +1172 -131
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +24 -2
- package/dist/spark.js +89 -3
- package/dist/spark.js.map +1 -1
- package/dist/{types-S8ggQY2n.d.ts → types-BuJ4boaq.d.ts} +160 -5
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +14 -7
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SplatCollisionProxyPreview.tsx +350 -0
- package/src/components/VisualScenario.tsx +287 -11
- package/src/core/MujocoSimProvider.tsx +374 -30
- package/src/core/SceneLoader.ts +13 -0
- package/src/hooks/useMountedCameraSequenceRecorder.ts +155 -0
- package/src/index.ts +80 -0
- package/src/rendering/cameraFrameCapture.ts +195 -26
- package/src/rendering/cameraFrameSource.ts +747 -0
- package/src/spark.tsx +144 -0
- package/src/types.ts +166 -4
- package/src/vite.ts +14 -6
- package/dist/chunk-33CV6HSV.js.map +0 -1
|
@@ -8,20 +8,26 @@ import type { ThreeElements } from '@react-three/fiber';
|
|
|
8
8
|
import type { ReactNode } from 'react';
|
|
9
9
|
import { useEffect, useMemo } from 'react';
|
|
10
10
|
import * as THREE from 'three';
|
|
11
|
+
import { SplatEnvironmentReadinessStatus } from '../types';
|
|
11
12
|
import type {
|
|
12
13
|
PairedSplatEnvironmentConfig,
|
|
13
14
|
ScenarioMaterialConfig,
|
|
14
15
|
SceneConfig,
|
|
15
16
|
SplatCollisionProxyConfig,
|
|
17
|
+
SplatEnvironmentReadiness,
|
|
16
18
|
SplatEnvironmentMetadata,
|
|
17
19
|
SplatEnvironmentMetadataInput,
|
|
18
20
|
SplatFormat,
|
|
19
21
|
SplatRendererKind,
|
|
22
|
+
SplatSceneConfigInput,
|
|
23
|
+
SplatSceneConfigState,
|
|
20
24
|
SplatSceneInput,
|
|
21
25
|
ScenarioLightingPreset,
|
|
22
26
|
ScenarioLightingProps,
|
|
23
27
|
SplatEnvironmentProps,
|
|
24
28
|
VisualScenarioConfig,
|
|
29
|
+
VisualScenarioExecutionContext,
|
|
30
|
+
VisualScenarioExecutionContextInput,
|
|
25
31
|
VisualScenarioEffectsProps,
|
|
26
32
|
} from '../types';
|
|
27
33
|
|
|
@@ -110,6 +116,84 @@ export function getScenarioCameraPosition(
|
|
|
110
116
|
];
|
|
111
117
|
}
|
|
112
118
|
|
|
119
|
+
export function useVisualScenarioExecutionContext({
|
|
120
|
+
scenario,
|
|
121
|
+
environment,
|
|
122
|
+
renderer,
|
|
123
|
+
variantId,
|
|
124
|
+
enabled,
|
|
125
|
+
}: VisualScenarioExecutionContextInput): VisualScenarioExecutionContext {
|
|
126
|
+
return useMemo(
|
|
127
|
+
() =>
|
|
128
|
+
createVisualScenarioExecutionContext({
|
|
129
|
+
scenario,
|
|
130
|
+
environment,
|
|
131
|
+
renderer,
|
|
132
|
+
variantId,
|
|
133
|
+
enabled,
|
|
134
|
+
}),
|
|
135
|
+
[enabled, environment, renderer, scenario, variantId]
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function createVisualScenarioExecutionContext({
|
|
140
|
+
scenario,
|
|
141
|
+
environment,
|
|
142
|
+
renderer,
|
|
143
|
+
variantId,
|
|
144
|
+
enabled = true,
|
|
145
|
+
}: VisualScenarioExecutionContextInput): VisualScenarioExecutionContext {
|
|
146
|
+
const pairedEnvironment =
|
|
147
|
+
environment ??
|
|
148
|
+
(scenario ? createPairedSplatEnvironment(scenario, { renderer }) : undefined);
|
|
149
|
+
const splat = scenario?.splat;
|
|
150
|
+
const collisionProxy =
|
|
151
|
+
pairedEnvironment?.collisionProxy ?? splat?.collisionProxy ?? undefined;
|
|
152
|
+
const readiness = getSplatEnvironmentReadiness({
|
|
153
|
+
environment: pairedEnvironment,
|
|
154
|
+
scenario,
|
|
155
|
+
renderer,
|
|
156
|
+
enabled,
|
|
157
|
+
});
|
|
158
|
+
const format =
|
|
159
|
+
pairedEnvironment?.splat.format ?? splat?.format ?? readiness.format ?? 'spz';
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
scenarioId: scenario?.id ?? pairedEnvironment?.id ?? 'visual-scenario',
|
|
163
|
+
scenarioLabel:
|
|
164
|
+
scenario?.label ?? pairedEnvironment?.label ?? 'Visual scenario',
|
|
165
|
+
variantId,
|
|
166
|
+
seed: scenario?.seed ?? 0,
|
|
167
|
+
lighting: scenario?.lighting ?? 'studio',
|
|
168
|
+
environment: scenario?.environment,
|
|
169
|
+
camera: {
|
|
170
|
+
jitter: scenario?.camera?.jitter ?? 0,
|
|
171
|
+
exposure: scenario?.camera?.exposure ?? 1,
|
|
172
|
+
noise: scenario?.camera?.noise ?? 0,
|
|
173
|
+
blur: scenario?.camera?.blur ?? 0,
|
|
174
|
+
},
|
|
175
|
+
materials: {
|
|
176
|
+
randomizeObjectColors: Boolean(
|
|
177
|
+
scenario?.materials?.randomizeObjectColors
|
|
178
|
+
),
|
|
179
|
+
randomizeTableMaterial: Boolean(
|
|
180
|
+
scenario?.materials?.randomizeTableMaterial
|
|
181
|
+
),
|
|
182
|
+
roughness: scenario?.materials?.roughness,
|
|
183
|
+
metalness: scenario?.materials?.metalness,
|
|
184
|
+
},
|
|
185
|
+
splatEnabled: Boolean(splat?.enabled || pairedEnvironment),
|
|
186
|
+
splatSrc: pairedEnvironment?.splat.src ?? splat?.src,
|
|
187
|
+
splatFormat: format,
|
|
188
|
+
splatRenderer: renderer ?? pairedEnvironment?.splat.renderer,
|
|
189
|
+
collisionProxyXmlPath: collisionProxy?.xmlPath,
|
|
190
|
+
collisionProxyStatus: collisionProxy?.status,
|
|
191
|
+
collisionProxyPrimitives: collisionProxy?.primitives ?? [],
|
|
192
|
+
readiness,
|
|
193
|
+
transformSource: 'visualScenario.camera',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
113
197
|
export function VisualScenarioEffects(props: VisualScenarioEffectsProps) {
|
|
114
198
|
useVisualScenarioEffects(props);
|
|
115
199
|
return null;
|
|
@@ -277,27 +361,208 @@ export function useSplatEnvironment({
|
|
|
277
361
|
scenarioEnvironment?.collisionProxy ??
|
|
278
362
|
scenario?.splat?.collisionProxy ??
|
|
279
363
|
undefined;
|
|
364
|
+
const readiness = useMemo(
|
|
365
|
+
() =>
|
|
366
|
+
getSplatEnvironmentReadiness({
|
|
367
|
+
environment: scenarioEnvironment,
|
|
368
|
+
scenario,
|
|
369
|
+
renderer,
|
|
370
|
+
src: resolvedSrc,
|
|
371
|
+
format: resolvedFormat,
|
|
372
|
+
collisionProxy: resolvedCollisionProxy,
|
|
373
|
+
}),
|
|
374
|
+
[
|
|
375
|
+
collisionProxy,
|
|
376
|
+
renderer,
|
|
377
|
+
resolvedCollisionProxy,
|
|
378
|
+
resolvedFormat,
|
|
379
|
+
resolvedSrc,
|
|
380
|
+
scenario,
|
|
381
|
+
scenarioEnvironment,
|
|
382
|
+
]
|
|
383
|
+
);
|
|
280
384
|
|
|
281
385
|
return useMemo(
|
|
282
386
|
() => ({
|
|
283
387
|
src: resolvedSrc,
|
|
284
388
|
format: resolvedFormat,
|
|
285
389
|
collisionProxy: resolvedCollisionProxy,
|
|
390
|
+
readiness,
|
|
286
391
|
userData: createSplatEnvironmentUserData({
|
|
287
392
|
environment: scenarioEnvironment,
|
|
288
393
|
src: resolvedSrc,
|
|
289
394
|
format: resolvedFormat,
|
|
290
395
|
collisionProxy: resolvedCollisionProxy,
|
|
396
|
+
readiness,
|
|
291
397
|
}),
|
|
292
398
|
}),
|
|
293
|
-
[
|
|
399
|
+
[
|
|
400
|
+
scenarioEnvironment,
|
|
401
|
+
resolvedSrc,
|
|
402
|
+
resolvedFormat,
|
|
403
|
+
resolvedCollisionProxy,
|
|
404
|
+
readiness,
|
|
405
|
+
]
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Resolve a visual scenario's paired splat environment and compose its MJCF
|
|
411
|
+
* collision proxy into a MuJoCo scene config.
|
|
412
|
+
*
|
|
413
|
+
* This hook is renderer-agnostic: apps can use it with Spark, another 3DGS
|
|
414
|
+
* renderer, or their own Three scene objects while keeping physics collision
|
|
415
|
+
* files paired with the visual splat metadata.
|
|
416
|
+
*/
|
|
417
|
+
export function useSplatSceneConfig({
|
|
418
|
+
sceneConfig,
|
|
419
|
+
scenario,
|
|
420
|
+
environment,
|
|
421
|
+
enabled = true,
|
|
422
|
+
renderer,
|
|
423
|
+
}: SplatSceneConfigInput): SplatSceneConfigState {
|
|
424
|
+
return useMemo(
|
|
425
|
+
() =>
|
|
426
|
+
createSplatSceneConfig({
|
|
427
|
+
sceneConfig,
|
|
428
|
+
scenario,
|
|
429
|
+
environment,
|
|
430
|
+
enabled,
|
|
431
|
+
renderer,
|
|
432
|
+
}),
|
|
433
|
+
[enabled, environment, renderer, scenario, sceneConfig]
|
|
294
434
|
);
|
|
295
435
|
}
|
|
296
436
|
|
|
297
437
|
/**
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
438
|
+
* Resolve a visual scenario's paired splat environment without requiring React.
|
|
439
|
+
*
|
|
440
|
+
* Use this in codegen, import validators, backend handoff metadata, or app code
|
|
441
|
+
* that needs the same behavior as `useSplatSceneConfig` outside a component.
|
|
442
|
+
*/
|
|
443
|
+
export function createSplatSceneConfig({
|
|
444
|
+
sceneConfig,
|
|
445
|
+
scenario,
|
|
446
|
+
environment,
|
|
447
|
+
enabled = true,
|
|
448
|
+
renderer,
|
|
449
|
+
}: SplatSceneConfigInput): SplatSceneConfigState {
|
|
450
|
+
const resolvedEnvironment = enabled
|
|
451
|
+
? environment ??
|
|
452
|
+
(scenario
|
|
453
|
+
? createPairedSplatEnvironment(scenario, { renderer })
|
|
454
|
+
: undefined)
|
|
455
|
+
: undefined;
|
|
456
|
+
const readiness = getSplatEnvironmentReadiness({
|
|
457
|
+
environment: resolvedEnvironment,
|
|
458
|
+
scenario,
|
|
459
|
+
renderer,
|
|
460
|
+
enabled,
|
|
461
|
+
});
|
|
462
|
+
const resolvedSceneConfig = resolvedEnvironment
|
|
463
|
+
? withSplatEnvironment(sceneConfig, resolvedEnvironment, { renderer })
|
|
464
|
+
: sceneConfig;
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
environment: resolvedEnvironment,
|
|
468
|
+
sceneConfig: resolvedSceneConfig,
|
|
469
|
+
enabled:
|
|
470
|
+
enabled && readiness.status !== SplatEnvironmentReadinessStatus.Disabled,
|
|
471
|
+
readiness,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function getSplatEnvironmentReadiness({
|
|
476
|
+
environment,
|
|
477
|
+
scenario,
|
|
478
|
+
renderer,
|
|
479
|
+
src,
|
|
480
|
+
format,
|
|
481
|
+
collisionProxy,
|
|
482
|
+
enabled = true,
|
|
483
|
+
}: {
|
|
484
|
+
environment?: PairedSplatEnvironmentConfig;
|
|
485
|
+
scenario?: Pick<VisualScenarioConfig, 'splat'>;
|
|
486
|
+
renderer?: SplatRendererKind;
|
|
487
|
+
src?: string;
|
|
488
|
+
format?: SplatFormat;
|
|
489
|
+
collisionProxy?: SplatCollisionProxyConfig;
|
|
490
|
+
enabled?: boolean;
|
|
491
|
+
}): SplatEnvironmentReadiness {
|
|
492
|
+
const splat = scenario?.splat;
|
|
493
|
+
const resolvedSrc = src ?? environment?.splat.src ?? splat?.src;
|
|
494
|
+
const resolvedFormat =
|
|
495
|
+
format ?? environment?.splat.format ?? splat?.format ?? 'spz';
|
|
496
|
+
const resolvedRenderer = renderer ?? environment?.splat.renderer;
|
|
497
|
+
const resolvedCollisionProxy =
|
|
498
|
+
collisionProxy ?? environment?.collisionProxy ?? splat?.collisionProxy ?? undefined;
|
|
499
|
+
const requiresCollisionProxy = splat?.requiresCollisionProxy ?? true;
|
|
500
|
+
|
|
501
|
+
if (!enabled || (splat && splat.enabled === false && !environment)) {
|
|
502
|
+
return {
|
|
503
|
+
status: SplatEnvironmentReadinessStatus.Disabled,
|
|
504
|
+
ready: false,
|
|
505
|
+
requiresCollisionProxy,
|
|
506
|
+
missing: [],
|
|
507
|
+
format: resolvedFormat,
|
|
508
|
+
renderer: resolvedRenderer,
|
|
509
|
+
message: 'Splat environment is disabled.',
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (!resolvedSrc) {
|
|
514
|
+
return {
|
|
515
|
+
status: SplatEnvironmentReadinessStatus.MissingSplat,
|
|
516
|
+
ready: false,
|
|
517
|
+
requiresCollisionProxy,
|
|
518
|
+
missing: ['splat'],
|
|
519
|
+
format: resolvedFormat,
|
|
520
|
+
renderer: resolvedRenderer,
|
|
521
|
+
message: 'Splat environment is missing a visual asset source.',
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (resolvedRenderer === 'spark' && resolvedFormat !== 'spz') {
|
|
526
|
+
return {
|
|
527
|
+
status: SplatEnvironmentReadinessStatus.UnsupportedFormat,
|
|
528
|
+
ready: false,
|
|
529
|
+
requiresCollisionProxy,
|
|
530
|
+
missing: [],
|
|
531
|
+
format: resolvedFormat,
|
|
532
|
+
renderer: resolvedRenderer,
|
|
533
|
+
message: `Spark splat rendering requires .spz assets; received ${resolvedFormat}.`,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (requiresCollisionProxy && !resolvedCollisionProxy?.xmlPath) {
|
|
538
|
+
return {
|
|
539
|
+
status: SplatEnvironmentReadinessStatus.MissingCollisionProxy,
|
|
540
|
+
ready: false,
|
|
541
|
+
requiresCollisionProxy,
|
|
542
|
+
missing: ['collisionProxy'],
|
|
543
|
+
format: resolvedFormat,
|
|
544
|
+
renderer: resolvedRenderer,
|
|
545
|
+
message: 'Splat environment is missing paired MJCF collision proxy XML.',
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
status: SplatEnvironmentReadinessStatus.Ready,
|
|
551
|
+
ready: true,
|
|
552
|
+
requiresCollisionProxy,
|
|
553
|
+
missing: [],
|
|
554
|
+
format: resolvedFormat,
|
|
555
|
+
renderer: resolvedRenderer,
|
|
556
|
+
message: requiresCollisionProxy
|
|
557
|
+
? 'Splat environment has visual asset and collision proxy metadata.'
|
|
558
|
+
: 'Splat environment has a visual asset and does not require collision proxy metadata.',
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Convert a generic visual scenario splat block into a composable splat
|
|
564
|
+
* environment config. Visual-only splats are valid; readiness reports whether
|
|
565
|
+
* a paired MJCF collision proxy is required before training/physics handoff.
|
|
301
566
|
*/
|
|
302
567
|
export function createPairedSplatEnvironment(
|
|
303
568
|
scenario: Pick<VisualScenarioConfig, 'id' | 'label' | 'environment' | 'splat'>,
|
|
@@ -311,7 +576,7 @@ export function createPairedSplatEnvironment(
|
|
|
311
576
|
const splat = scenario.splat;
|
|
312
577
|
const collisionProxy = splat?.collisionProxy;
|
|
313
578
|
|
|
314
|
-
if (!splat?.enabled || !splat.src
|
|
579
|
+
if (!splat?.enabled || !splat.src) {
|
|
315
580
|
return undefined;
|
|
316
581
|
}
|
|
317
582
|
|
|
@@ -328,15 +593,22 @@ export function createPairedSplatEnvironment(
|
|
|
328
593
|
format: splat.format ?? 'spz',
|
|
329
594
|
renderer: options.renderer,
|
|
330
595
|
},
|
|
331
|
-
collisionProxy:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
596
|
+
collisionProxy: collisionProxy?.xmlPath
|
|
597
|
+
? {
|
|
598
|
+
...collisionProxy,
|
|
599
|
+
xmlPath: collisionProxy.xmlPath,
|
|
600
|
+
}
|
|
601
|
+
: undefined,
|
|
335
602
|
};
|
|
336
603
|
}
|
|
337
604
|
|
|
338
605
|
function isPairedSplatEnvironment(input: SplatSceneInput): input is PairedSplatEnvironmentConfig {
|
|
339
|
-
return
|
|
606
|
+
return (
|
|
607
|
+
!!input &&
|
|
608
|
+
'splat' in input &&
|
|
609
|
+
!!input.splat &&
|
|
610
|
+
!('enabled' in input.splat)
|
|
611
|
+
);
|
|
340
612
|
}
|
|
341
613
|
|
|
342
614
|
function sceneRelativePath(sceneConfig: SceneConfig, path: string): string {
|
|
@@ -376,7 +648,7 @@ export function withSplatEnvironment(
|
|
|
376
648
|
: input
|
|
377
649
|
? createPairedSplatEnvironment(input, options)
|
|
378
650
|
: undefined;
|
|
379
|
-
const xmlPath = environment?.collisionProxy
|
|
651
|
+
const xmlPath = environment?.collisionProxy?.xmlPath;
|
|
380
652
|
if (!xmlPath) return sceneConfig;
|
|
381
653
|
|
|
382
654
|
return {
|
|
@@ -393,11 +665,13 @@ export function createSplatEnvironmentUserData({
|
|
|
393
665
|
src,
|
|
394
666
|
format = 'spz',
|
|
395
667
|
collisionProxy,
|
|
668
|
+
readiness,
|
|
396
669
|
}: {
|
|
397
670
|
environment?: PairedSplatEnvironmentConfig;
|
|
398
671
|
src?: string;
|
|
399
672
|
format?: SplatFormat;
|
|
400
673
|
collisionProxy?: SplatCollisionProxyConfig;
|
|
674
|
+
readiness?: SplatEnvironmentReadiness;
|
|
401
675
|
}) {
|
|
402
676
|
return {
|
|
403
677
|
role: 'splat-environment',
|
|
@@ -409,6 +683,8 @@ export function createSplatEnvironmentUserData({
|
|
|
409
683
|
collisionProxyStatus: collisionProxy?.status ?? 'missing',
|
|
410
684
|
collisionProxyXmlPath: collisionProxy?.xmlPath,
|
|
411
685
|
collisionProxyPrimitives: collisionProxy?.primitives ?? [],
|
|
686
|
+
readinessStatus: readiness?.status,
|
|
687
|
+
readinessMessage: readiness?.message,
|
|
412
688
|
};
|
|
413
689
|
}
|
|
414
690
|
|