mujoco-react 9.1.0 → 9.3.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 +121 -17
- package/dist/{chunk-33CV6HSV.js → chunk-T3GVZJ4F.js} +222 -8
- package/dist/chunk-T3GVZJ4F.js.map +1 -0
- package/dist/index.d.ts +198 -6
- package/dist/index.js +1109 -216
- 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-C5gTvR7b.d.ts → types-oxbxOkAx.d.ts} +190 -2
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +6 -3
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/VisualScenario.tsx +178 -1
- package/src/core/MujocoSimProvider.tsx +473 -11
- package/src/core/SceneLoader.ts +13 -0
- package/src/core/createController.tsx +6 -2
- package/src/hooks/useCameraFrameCapture.ts +94 -0
- package/src/hooks/useCameraSequenceRecorder.ts +59 -0
- package/src/hooks/useMountedCameraSequenceRecorder.ts +107 -0
- package/src/index.ts +67 -0
- package/src/rendering/cameraFrameCapture.ts +353 -0
- package/src/rendering/cameraFrameSource.ts +375 -0
- package/src/spark.tsx +144 -0
- package/src/types.ts +212 -2
- package/src/vite.ts +5 -2
- package/dist/chunk-33CV6HSV.js.map +0 -1
package/src/spark.tsx
CHANGED
|
@@ -15,9 +15,14 @@ import * as THREE from 'three';
|
|
|
15
15
|
import {
|
|
16
16
|
SplatEnvironment,
|
|
17
17
|
useSplatEnvironment,
|
|
18
|
+
useSplatSceneConfig,
|
|
18
19
|
} from './components/VisualScenario';
|
|
19
20
|
import type {
|
|
21
|
+
PairedSplatEnvironmentConfig,
|
|
22
|
+
SceneConfig,
|
|
20
23
|
SplatEnvironmentProps,
|
|
24
|
+
SplatEnvironmentReadiness,
|
|
25
|
+
VisualScenarioConfig,
|
|
21
26
|
} from './types';
|
|
22
27
|
|
|
23
28
|
type SparkModule = typeof import('@sparkjsdev/spark');
|
|
@@ -26,9 +31,22 @@ type SparkSplatMeshInstance = InstanceType<SparkModule['SplatMesh']>;
|
|
|
26
31
|
type SparkDisposable = {
|
|
27
32
|
dispose?: () => unknown;
|
|
28
33
|
};
|
|
34
|
+
type SparkWorkerMessage = {
|
|
35
|
+
reject?: (error: unknown) => void;
|
|
36
|
+
};
|
|
37
|
+
type SparkWorkerLike = {
|
|
38
|
+
messages?: Record<string, SparkWorkerMessage>;
|
|
39
|
+
};
|
|
40
|
+
type SparkResourceWithWorkers = SparkDisposable & {
|
|
41
|
+
worker?: SparkWorkerLike;
|
|
42
|
+
sortWorker?: SparkWorkerLike;
|
|
43
|
+
lodWorker?: SparkWorkerLike;
|
|
44
|
+
};
|
|
29
45
|
|
|
30
46
|
export type SparkSplatStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
31
47
|
|
|
48
|
+
let sparkDisposeRejectionHandlerRegistered = false;
|
|
49
|
+
|
|
32
50
|
export interface SparkSplatLifecycle {
|
|
33
51
|
status: SparkSplatStatus;
|
|
34
52
|
error: Error | null;
|
|
@@ -39,6 +57,18 @@ export interface SparkSplatLifecycle {
|
|
|
39
57
|
reset: () => void;
|
|
40
58
|
}
|
|
41
59
|
|
|
60
|
+
export interface SparkSplatEnvironmentState {
|
|
61
|
+
environment: PairedSplatEnvironmentConfig | undefined;
|
|
62
|
+
sceneConfig: SceneConfig;
|
|
63
|
+
readiness: SplatEnvironmentReadiness;
|
|
64
|
+
lifecycle: SparkSplatLifecycle;
|
|
65
|
+
props: Pick<
|
|
66
|
+
SparkSplatEnvironmentProps,
|
|
67
|
+
'environment' | 'scenario' | 'src' | 'format' | 'onStatusChange' | 'onError'
|
|
68
|
+
>;
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
42
72
|
export interface SparkSplatEnvironmentProps extends SplatEnvironmentProps {
|
|
43
73
|
/** Enable Spark LoD handling for large splat assets. Default: true. */
|
|
44
74
|
lod?: boolean | 'quality';
|
|
@@ -53,6 +83,67 @@ export interface SparkSplatEnvironmentProps extends SplatEnvironmentProps {
|
|
|
53
83
|
onError?: (error: Error) => void;
|
|
54
84
|
}
|
|
55
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a visual scenario's paired splat environment, compose its MJCF
|
|
88
|
+
* collision proxy into the MuJoCo scene config, and expose Spark lifecycle
|
|
89
|
+
* props for `<SparkSplatEnvironment />`.
|
|
90
|
+
*/
|
|
91
|
+
export function useSparkSplatEnvironment({
|
|
92
|
+
sceneConfig,
|
|
93
|
+
scenario,
|
|
94
|
+
environment,
|
|
95
|
+
enabled = true,
|
|
96
|
+
renderer = 'spark',
|
|
97
|
+
onError,
|
|
98
|
+
onStatusChange,
|
|
99
|
+
}: {
|
|
100
|
+
sceneConfig: SceneConfig;
|
|
101
|
+
scenario?: VisualScenarioConfig;
|
|
102
|
+
environment?: PairedSplatEnvironmentConfig;
|
|
103
|
+
enabled?: boolean;
|
|
104
|
+
renderer?: 'spark';
|
|
105
|
+
onError?: (error: Error) => void;
|
|
106
|
+
onStatusChange?: (status: SparkSplatStatus) => void;
|
|
107
|
+
}): SparkSplatEnvironmentState {
|
|
108
|
+
const splatScene = useSplatSceneConfig({
|
|
109
|
+
sceneConfig,
|
|
110
|
+
scenario,
|
|
111
|
+
environment,
|
|
112
|
+
enabled,
|
|
113
|
+
renderer,
|
|
114
|
+
});
|
|
115
|
+
const metadata = useSplatEnvironment({
|
|
116
|
+
scenario,
|
|
117
|
+
environment: splatScene.environment,
|
|
118
|
+
renderer,
|
|
119
|
+
});
|
|
120
|
+
const renderEnabled = enabled && Boolean(metadata.src);
|
|
121
|
+
const readiness = enabled ? metadata.readiness : splatScene.readiness;
|
|
122
|
+
const lifecycle = useSparkSplatLifecycle({
|
|
123
|
+
enabled: renderEnabled,
|
|
124
|
+
onError,
|
|
125
|
+
onStatusChange,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return useMemo(
|
|
129
|
+
() => ({
|
|
130
|
+
environment: splatScene.environment,
|
|
131
|
+
sceneConfig: splatScene.sceneConfig,
|
|
132
|
+
readiness,
|
|
133
|
+
lifecycle,
|
|
134
|
+
props: {
|
|
135
|
+
environment: splatScene.environment,
|
|
136
|
+
scenario: enabled ? scenario : undefined,
|
|
137
|
+
src: enabled ? metadata.src : undefined,
|
|
138
|
+
format: metadata.format,
|
|
139
|
+
...lifecycle.props,
|
|
140
|
+
},
|
|
141
|
+
enabled: renderEnabled,
|
|
142
|
+
}),
|
|
143
|
+
[enabled, lifecycle, metadata, readiness, renderEnabled, scenario, splatScene]
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
56
147
|
/**
|
|
57
148
|
* Tracks Spark 3DGS loading state for UI that wraps `SparkSplatEnvironment`.
|
|
58
149
|
*
|
|
@@ -177,6 +268,7 @@ export function SparkSplatEnvironment({
|
|
|
177
268
|
|
|
178
269
|
useEffect(() => {
|
|
179
270
|
let disposed = false;
|
|
271
|
+
ensureSparkDisposeRejectionHandler();
|
|
180
272
|
|
|
181
273
|
function setLifecycleStatus(nextStatus: SparkSplatStatus) {
|
|
182
274
|
setStatus(nextStatus);
|
|
@@ -306,6 +398,7 @@ export function SparkSplatEnvironment({
|
|
|
306
398
|
|
|
307
399
|
function safelyDisposeSparkResource(resource: SparkDisposable) {
|
|
308
400
|
try {
|
|
401
|
+
silenceSparkWorkerTerminateRejections(resource);
|
|
309
402
|
const result = resource.dispose?.();
|
|
310
403
|
if (isPromiseLike(result)) {
|
|
311
404
|
void Promise.resolve(result).catch(handleSparkDisposeError);
|
|
@@ -324,6 +417,33 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
|
324
417
|
);
|
|
325
418
|
}
|
|
326
419
|
|
|
420
|
+
function silenceSparkWorkerTerminateRejections(resource: SparkDisposable) {
|
|
421
|
+
const workers = getSparkWorkers(resource);
|
|
422
|
+
for (const worker of workers) {
|
|
423
|
+
if (!worker.messages) continue;
|
|
424
|
+
|
|
425
|
+
for (const message of Object.values(worker.messages)) {
|
|
426
|
+
const reject = message.reject;
|
|
427
|
+
if (!reject) continue;
|
|
428
|
+
|
|
429
|
+
message.reject = (error: unknown) => {
|
|
430
|
+
if (!isSparkWorkerTerminateError(error)) {
|
|
431
|
+
reject(error);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function getSparkWorkers(resource: SparkDisposable): SparkWorkerLike[] {
|
|
439
|
+
const sparkResource = resource as SparkResourceWithWorkers;
|
|
440
|
+
return [
|
|
441
|
+
sparkResource.worker,
|
|
442
|
+
sparkResource.sortWorker,
|
|
443
|
+
sparkResource.lodWorker,
|
|
444
|
+
].filter((worker): worker is SparkWorkerLike => Boolean(worker));
|
|
445
|
+
}
|
|
446
|
+
|
|
327
447
|
function handleSparkDisposeError(error: unknown) {
|
|
328
448
|
if (
|
|
329
449
|
error instanceof Error &&
|
|
@@ -334,3 +454,27 @@ function handleSparkDisposeError(error: unknown) {
|
|
|
334
454
|
|
|
335
455
|
console.warn('[mujoco-react] Spark resource disposal failed.', error);
|
|
336
456
|
}
|
|
457
|
+
|
|
458
|
+
function ensureSparkDisposeRejectionHandler() {
|
|
459
|
+
if (
|
|
460
|
+
sparkDisposeRejectionHandlerRegistered ||
|
|
461
|
+
typeof window === 'undefined' ||
|
|
462
|
+
typeof window.addEventListener !== 'function'
|
|
463
|
+
) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
sparkDisposeRejectionHandlerRegistered = true;
|
|
468
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
469
|
+
if (isSparkWorkerTerminateError(event.reason)) {
|
|
470
|
+
event.preventDefault();
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function isSparkWorkerTerminateError(reason: unknown) {
|
|
476
|
+
return (
|
|
477
|
+
reason instanceof Error &&
|
|
478
|
+
reason.message.toLowerCase().includes('worker terminate')
|
|
479
|
+
);
|
|
480
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -54,8 +54,9 @@ export type RobotJoints<TRobot extends string> = RobotResource<TRobot, 'joints'>
|
|
|
54
54
|
export type RobotSites<TRobot extends string> = RobotResource<TRobot, 'sites'>;
|
|
55
55
|
export type RobotGeoms<TRobot extends string> = RobotResource<TRobot, 'geoms'>;
|
|
56
56
|
export type RobotKeyframes<TRobot extends string> = RobotResource<TRobot, 'keyframes'>;
|
|
57
|
+
export type RobotCameras<TRobot extends string> = RobotResource<TRobot, 'cameras'>;
|
|
57
58
|
|
|
58
|
-
export type RegisterResourceKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';
|
|
59
|
+
export type RegisterResourceKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes' | 'cameras';
|
|
59
60
|
export type RobotResourceObject<TRobot extends string, TKey extends RegisterResourceKey> =
|
|
60
61
|
string extends RobotResource<TRobot, TKey>
|
|
61
62
|
? Record<string, string>
|
|
@@ -73,7 +74,7 @@ type RuntimeRobotResources = Record<string, Record<RegisterResourceKey, Record<s
|
|
|
73
74
|
type RuntimeRobotResourceRegistration = Readonly<Record<string, Readonly<Record<RegisterResourceKey, Readonly<Record<string, string>>>>>>;
|
|
74
75
|
|
|
75
76
|
const runtimeRobotResources: RuntimeRobotResources = {};
|
|
76
|
-
const REGISTER_RESOURCE_KEYS: RegisterResourceKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes'];
|
|
77
|
+
const REGISTER_RESOURCE_KEYS: RegisterResourceKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes', 'cameras'];
|
|
77
78
|
|
|
78
79
|
function createEmptyRuntimeResources(): Record<RegisterResourceKey, Record<string, string>> {
|
|
79
80
|
return {
|
|
@@ -84,6 +85,7 @@ function createEmptyRuntimeResources(): Record<RegisterResourceKey, Record<strin
|
|
|
84
85
|
sites: {},
|
|
85
86
|
geoms: {},
|
|
86
87
|
keyframes: {},
|
|
88
|
+
cameras: {},
|
|
87
89
|
};
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -134,6 +136,7 @@ export const RobotJoints: RobotResourceCategory<'joints'> = createResourceCatego
|
|
|
134
136
|
export const RobotSites: RobotResourceCategory<'sites'> = createResourceCategory('sites');
|
|
135
137
|
export const RobotGeoms: RobotResourceCategory<'geoms'> = createResourceCategory('geoms');
|
|
136
138
|
export const RobotKeyframes: RobotResourceCategory<'keyframes'> = createResourceCategory('keyframes');
|
|
139
|
+
export const RobotCameras: RobotResourceCategory<'cameras'> = createResourceCategory('cameras');
|
|
137
140
|
|
|
138
141
|
export type Actuators = Register extends { actuators: infer T extends string } ? T : string;
|
|
139
142
|
export type Sensors = Register extends { sensors: infer T extends string } ? T : string;
|
|
@@ -142,6 +145,7 @@ export type Joints = Register extends { joints: infer T extends string } ? T : s
|
|
|
142
145
|
export type Sites = Register extends { sites: infer T extends string } ? T : string;
|
|
143
146
|
export type Geoms = Register extends { geoms: infer T extends string } ? T : string;
|
|
144
147
|
export type Keyframes = Register extends { keyframes: infer T extends string } ? T : string;
|
|
148
|
+
export type Cameras = Register extends { cameras: infer T extends string } ? T : string;
|
|
145
149
|
|
|
146
150
|
// ---- MuJoCo WASM Types ----
|
|
147
151
|
|
|
@@ -209,6 +213,7 @@ export interface MujocoModel {
|
|
|
209
213
|
nflex: number;
|
|
210
214
|
nmesh: number;
|
|
211
215
|
nmat: number;
|
|
216
|
+
ncam?: number;
|
|
212
217
|
|
|
213
218
|
// Name tables
|
|
214
219
|
names: Int8Array;
|
|
@@ -220,6 +225,7 @@ export interface MujocoModel {
|
|
|
220
225
|
name_keyadr: Int32Array;
|
|
221
226
|
name_sensoradr: Int32Array;
|
|
222
227
|
name_tendonadr: Int32Array;
|
|
228
|
+
name_camadr?: Int32Array;
|
|
223
229
|
|
|
224
230
|
// Body
|
|
225
231
|
body_mass: Float64Array;
|
|
@@ -307,6 +313,12 @@ export interface MujocoModel {
|
|
|
307
313
|
light_exponent: Float32Array;
|
|
308
314
|
light_intensity: Float32Array;
|
|
309
315
|
|
|
316
|
+
// Camera
|
|
317
|
+
cam_bodyid?: Int32Array;
|
|
318
|
+
cam_pos?: Float64Array;
|
|
319
|
+
cam_quat?: Float64Array;
|
|
320
|
+
cam_fovy?: Float64Array;
|
|
321
|
+
|
|
310
322
|
// Tendon
|
|
311
323
|
ten_wrapadr: Int32Array;
|
|
312
324
|
ten_wrapnum: Int32Array;
|
|
@@ -350,6 +362,9 @@ export interface MujocoData {
|
|
|
350
362
|
qfrc_bias: Float64Array;
|
|
351
363
|
site_xpos: Float64Array;
|
|
352
364
|
site_xmat: Float64Array;
|
|
365
|
+
cam_xpos?: Float64Array;
|
|
366
|
+
cam_xmat?: Float64Array;
|
|
367
|
+
xmat?: Float64Array;
|
|
353
368
|
sensordata: Float64Array;
|
|
354
369
|
ncon: number;
|
|
355
370
|
contact: MujocoContactArray;
|
|
@@ -683,6 +698,15 @@ export interface SensorInfo {
|
|
|
683
698
|
adr: number;
|
|
684
699
|
}
|
|
685
700
|
|
|
701
|
+
export interface CameraInfo {
|
|
702
|
+
id: number;
|
|
703
|
+
name: string;
|
|
704
|
+
bodyId: number;
|
|
705
|
+
fov: number | null;
|
|
706
|
+
position: [number, number, number] | null;
|
|
707
|
+
quaternion: [number, number, number, number] | null;
|
|
708
|
+
}
|
|
709
|
+
|
|
686
710
|
// ---- Contacts (spec 2.4, 2.5) ----
|
|
687
711
|
|
|
688
712
|
export interface ContactInfo {
|
|
@@ -908,6 +932,27 @@ export interface PairedSplatEnvironmentConfig {
|
|
|
908
932
|
collisionProxy: SplatCollisionProxyConfig & { xmlPath: string };
|
|
909
933
|
}
|
|
910
934
|
|
|
935
|
+
export const SplatEnvironmentReadinessStatus = {
|
|
936
|
+
Disabled: 'disabled',
|
|
937
|
+
MissingSplat: 'missing-splat',
|
|
938
|
+
MissingCollisionProxy: 'missing-collision-proxy',
|
|
939
|
+
UnsupportedFormat: 'unsupported-format',
|
|
940
|
+
Ready: 'ready',
|
|
941
|
+
} as const;
|
|
942
|
+
|
|
943
|
+
export type SplatEnvironmentReadinessStatus =
|
|
944
|
+
(typeof SplatEnvironmentReadinessStatus)[keyof typeof SplatEnvironmentReadinessStatus];
|
|
945
|
+
|
|
946
|
+
export interface SplatEnvironmentReadiness {
|
|
947
|
+
status: SplatEnvironmentReadinessStatus;
|
|
948
|
+
ready: boolean;
|
|
949
|
+
requiresCollisionProxy: boolean;
|
|
950
|
+
missing: Array<'splat' | 'collisionProxy'>;
|
|
951
|
+
format?: SplatFormat;
|
|
952
|
+
renderer?: SplatRendererKind;
|
|
953
|
+
message: string;
|
|
954
|
+
}
|
|
955
|
+
|
|
911
956
|
export interface SplatEnvironmentMetadataInput {
|
|
912
957
|
environment?: PairedSplatEnvironmentConfig;
|
|
913
958
|
scenario?: VisualScenarioConfig;
|
|
@@ -921,6 +966,7 @@ export interface SplatEnvironmentMetadata {
|
|
|
921
966
|
src?: string;
|
|
922
967
|
format: SplatFormat;
|
|
923
968
|
collisionProxy?: SplatCollisionProxyConfig;
|
|
969
|
+
readiness: SplatEnvironmentReadiness;
|
|
924
970
|
userData: Record<string, unknown>;
|
|
925
971
|
}
|
|
926
972
|
|
|
@@ -930,6 +976,21 @@ export type SplatSceneInput =
|
|
|
930
976
|
| undefined
|
|
931
977
|
| null;
|
|
932
978
|
|
|
979
|
+
export interface SplatSceneConfigInput {
|
|
980
|
+
sceneConfig: SceneConfig;
|
|
981
|
+
scenario?: VisualScenarioConfig;
|
|
982
|
+
environment?: PairedSplatEnvironmentConfig;
|
|
983
|
+
enabled?: boolean;
|
|
984
|
+
renderer?: SplatRendererKind;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
export interface SplatSceneConfigState {
|
|
988
|
+
environment: PairedSplatEnvironmentConfig | undefined;
|
|
989
|
+
sceneConfig: SceneConfig;
|
|
990
|
+
enabled: boolean;
|
|
991
|
+
readiness: SplatEnvironmentReadiness;
|
|
992
|
+
}
|
|
993
|
+
|
|
933
994
|
export interface VisualScenarioConfig {
|
|
934
995
|
id?: string;
|
|
935
996
|
label?: string;
|
|
@@ -1079,6 +1140,7 @@ export interface MujocoSimAPI {
|
|
|
1079
1140
|
getSites(): SiteInfo[];
|
|
1080
1141
|
getActuators(): ActuatorInfo[];
|
|
1081
1142
|
getSensors(): SensorInfo[];
|
|
1143
|
+
getCameras(): CameraInfo[];
|
|
1082
1144
|
|
|
1083
1145
|
// Model parameters (spec 5.3)
|
|
1084
1146
|
getModelOption(): ModelOptions;
|
|
@@ -1104,6 +1166,9 @@ export interface MujocoSimAPI {
|
|
|
1104
1166
|
getCanvasSnapshot(width?: number, height?: number, mimeType?: string): string;
|
|
1105
1167
|
captureFrame(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureResult>;
|
|
1106
1168
|
captureFrameBlob(options?: MujocoFrameCaptureOptions): Promise<FrameCaptureBlobResult>;
|
|
1169
|
+
captureCameraFrame(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureResult>;
|
|
1170
|
+
captureCameraFrameBlob(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureBlobResult>;
|
|
1171
|
+
recordCameraSequence(options: CameraFrameSequenceOptions): Promise<CameraFrameSequenceResult>;
|
|
1107
1172
|
project2DTo3D(
|
|
1108
1173
|
x: number,
|
|
1109
1174
|
y: number,
|
|
@@ -1164,6 +1229,151 @@ export interface FrameCaptureAPI {
|
|
|
1164
1229
|
reset: () => void;
|
|
1165
1230
|
}
|
|
1166
1231
|
|
|
1232
|
+
export type CameraFrameCaptureVector3 =
|
|
1233
|
+
| THREE.Vector3
|
|
1234
|
+
| readonly [number, number, number];
|
|
1235
|
+
|
|
1236
|
+
export type CameraFrameCaptureQuaternion =
|
|
1237
|
+
| THREE.Quaternion
|
|
1238
|
+
| readonly [number, number, number, number];
|
|
1239
|
+
|
|
1240
|
+
export interface CameraFrameCaptureOptions {
|
|
1241
|
+
/** Existing Three camera to clone before applying pose overrides. */
|
|
1242
|
+
camera?: THREE.Camera;
|
|
1243
|
+
/** Named MuJoCo `<camera>` to render from when available in the loaded model. */
|
|
1244
|
+
cameraName?: Cameras;
|
|
1245
|
+
/** Named MuJoCo site to use as the rendered camera pose. Useful for robot-mounted optical frames. */
|
|
1246
|
+
siteName?: Sites;
|
|
1247
|
+
/** Named MuJoCo body to use as the rendered camera pose. */
|
|
1248
|
+
bodyName?: Bodies;
|
|
1249
|
+
position?: CameraFrameCaptureVector3;
|
|
1250
|
+
lookAt?: CameraFrameCaptureVector3;
|
|
1251
|
+
quaternion?: CameraFrameCaptureQuaternion;
|
|
1252
|
+
up?: CameraFrameCaptureVector3;
|
|
1253
|
+
width?: number;
|
|
1254
|
+
height?: number;
|
|
1255
|
+
type?: string;
|
|
1256
|
+
quality?: number;
|
|
1257
|
+
fov?: number;
|
|
1258
|
+
near?: number;
|
|
1259
|
+
far?: number;
|
|
1260
|
+
/** Provenance for the camera pose used by the capture. Usually set by the MuJoCo provider. */
|
|
1261
|
+
source?: CameraFrameCaptureSource;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
export type CameraFrameCaptureSource =
|
|
1265
|
+
| { kind: 'mujoco-camera'; cameraName: Cameras }
|
|
1266
|
+
| { kind: 'mujoco-site'; siteName: Sites }
|
|
1267
|
+
| { kind: 'mujoco-body'; bodyName: Bodies }
|
|
1268
|
+
| { kind: 'custom-camera' }
|
|
1269
|
+
| { kind: 'explicit-pose' }
|
|
1270
|
+
| { kind: 'fallback-camera' };
|
|
1271
|
+
|
|
1272
|
+
export interface CameraFrameCaptureResult {
|
|
1273
|
+
canvas: HTMLCanvasElement;
|
|
1274
|
+
camera: THREE.Camera;
|
|
1275
|
+
dataUrl: string;
|
|
1276
|
+
type: string;
|
|
1277
|
+
width: number;
|
|
1278
|
+
height: number;
|
|
1279
|
+
source: CameraFrameCaptureSource;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
export interface CameraFrameCaptureBlobResult {
|
|
1283
|
+
canvas: HTMLCanvasElement;
|
|
1284
|
+
camera: THREE.Camera;
|
|
1285
|
+
blob: Blob;
|
|
1286
|
+
type: string;
|
|
1287
|
+
width: number;
|
|
1288
|
+
height: number;
|
|
1289
|
+
source: CameraFrameCaptureSource;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
export interface CameraFrameCaptureAPI {
|
|
1293
|
+
status: FrameCaptureStatus;
|
|
1294
|
+
error: Error | null;
|
|
1295
|
+
isCapturing: boolean;
|
|
1296
|
+
capture: (
|
|
1297
|
+
options?: CameraFrameCaptureOptions
|
|
1298
|
+
) => Promise<CameraFrameCaptureResult>;
|
|
1299
|
+
captureBlob: (
|
|
1300
|
+
options?: CameraFrameCaptureOptions
|
|
1301
|
+
) => Promise<CameraFrameCaptureBlobResult>;
|
|
1302
|
+
reset: () => void;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
export interface CameraFrameSequenceCamera extends CameraFrameCaptureOptions {
|
|
1306
|
+
key: string;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
export interface CameraFrameSequenceFrame {
|
|
1310
|
+
frameIndex: number;
|
|
1311
|
+
time: number;
|
|
1312
|
+
cameras: Record<string, CameraFrameCaptureResult>;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
export interface CameraFrameSequenceCameraSummary {
|
|
1316
|
+
key: string;
|
|
1317
|
+
width: number;
|
|
1318
|
+
height: number;
|
|
1319
|
+
source: CameraFrameCaptureSource;
|
|
1320
|
+
frameCount: number;
|
|
1321
|
+
firstFrameIndex: number | null;
|
|
1322
|
+
lastFrameIndex: number | null;
|
|
1323
|
+
firstTimestamp: number | null;
|
|
1324
|
+
lastTimestamp: number | null;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
export interface CameraFrameSequenceSampleInput extends PhysicsStepInput {
|
|
1328
|
+
frameIndex: number;
|
|
1329
|
+
time: number;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
export interface CameraFrameSequenceStepInput extends PhysicsStepInput {
|
|
1333
|
+
frameIndex: number;
|
|
1334
|
+
stepIndex: number;
|
|
1335
|
+
time: number;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
export interface CameraFrameSequenceOptions {
|
|
1339
|
+
cameras: readonly CameraFrameSequenceCamera[];
|
|
1340
|
+
frames: number;
|
|
1341
|
+
/** Number of MuJoCo steps between captured frames. Use 0 for static camera provenance captures. */
|
|
1342
|
+
stepsPerFrame?: number;
|
|
1343
|
+
reset?: boolean;
|
|
1344
|
+
captureInitialFrame?: boolean;
|
|
1345
|
+
retainFrames?: boolean;
|
|
1346
|
+
/**
|
|
1347
|
+
* Require each recorded stream to resolve from exactly one mounted MuJoCo
|
|
1348
|
+
* camera/site/body selector. Defaults to true because sequence recording is
|
|
1349
|
+
* intended for dataset/policy camera streams.
|
|
1350
|
+
*/
|
|
1351
|
+
requireMountedSources?: boolean;
|
|
1352
|
+
signal?: AbortSignal;
|
|
1353
|
+
/** Called after stepping and before image capture for this frame. Use this to record synchronized state/action rows. */
|
|
1354
|
+
onSample?: (input: CameraFrameSequenceSampleInput) => void | Promise<void>;
|
|
1355
|
+
/** Called before each MuJoCo step inside sequence recording. Use this to apply policy/control actions. */
|
|
1356
|
+
onBeforeStep?: (input: CameraFrameSequenceStepInput) => void | Promise<void>;
|
|
1357
|
+
/** Called after each MuJoCo step inside sequence recording. Use this for step-level telemetry. */
|
|
1358
|
+
onAfterStep?: (input: CameraFrameSequenceStepInput) => void | Promise<void>;
|
|
1359
|
+
onFrame?: (frame: CameraFrameSequenceFrame) => void | Promise<void>;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
export interface CameraFrameSequenceResult {
|
|
1363
|
+
frames: CameraFrameSequenceFrame[];
|
|
1364
|
+
cameraKeys: string[];
|
|
1365
|
+
cameraSummaries: Record<string, CameraFrameSequenceCameraSummary>;
|
|
1366
|
+
frameCount: number;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
export interface CameraFrameSequenceRecorderAPI {
|
|
1370
|
+
status: FrameCaptureStatus;
|
|
1371
|
+
error: Error | null;
|
|
1372
|
+
isRecording: boolean;
|
|
1373
|
+
record: (options: CameraFrameSequenceOptions) => Promise<CameraFrameSequenceResult>;
|
|
1374
|
+
reset: () => void;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1167
1377
|
// ---- Canvas Props ----
|
|
1168
1378
|
|
|
1169
1379
|
export type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
|
package/src/vite.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface MujocoRegisterCodegenResult {
|
|
|
38
38
|
counts: Record<RegisterKey, number>;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
type RegisterKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes';
|
|
41
|
+
type RegisterKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes' | 'cameras';
|
|
42
42
|
export type ModelInput = string | readonly string[] | Record<string, string>;
|
|
43
43
|
|
|
44
44
|
interface ModelEntry {
|
|
@@ -47,7 +47,7 @@ interface ModelEntry {
|
|
|
47
47
|
names: Record<RegisterKey, Set<string>>;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const REGISTER_KEYS: RegisterKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes'];
|
|
50
|
+
const REGISTER_KEYS: RegisterKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes', 'cameras'];
|
|
51
51
|
const MODEL_EXTENSIONS = new Set(['.xml', '.mjcf', '.urdf']);
|
|
52
52
|
|
|
53
53
|
function createEmptyNames(): Record<RegisterKey, Set<string>> {
|
|
@@ -59,6 +59,7 @@ function createEmptyNames(): Record<RegisterKey, Set<string>> {
|
|
|
59
59
|
sites: new Set(),
|
|
60
60
|
geoms: new Set(),
|
|
61
61
|
keyframes: new Set(),
|
|
62
|
+
cameras: new Set(),
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -166,6 +167,7 @@ async function scanModel(
|
|
|
166
167
|
collectSimpleTagNames(xml, 'joint', names.joints);
|
|
167
168
|
collectSimpleTagNames(xml, 'site', names.sites);
|
|
168
169
|
collectSimpleTagNames(xml, 'geom', names.geoms);
|
|
170
|
+
collectSimpleTagNames(xml, 'camera', names.cameras);
|
|
169
171
|
collectSimpleTagNames(xml, 'key', names.keyframes);
|
|
170
172
|
collectSectionNames(xml, 'actuator', names.actuators);
|
|
171
173
|
collectSectionNames(xml, 'sensor', names.sensors);
|
|
@@ -300,6 +302,7 @@ function renderNamespaceAliases(models: readonly ModelEntry[]): string {
|
|
|
300
302
|
sites: 'RobotSites',
|
|
301
303
|
geoms: 'RobotGeoms',
|
|
302
304
|
keyframes: 'RobotKeyframes',
|
|
305
|
+
cameras: 'RobotCameras',
|
|
303
306
|
};
|
|
304
307
|
|
|
305
308
|
const blocks = REGISTER_KEYS
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/VisualScenario.tsx"],"names":[],"mappings":"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","file":"chunk-33CV6HSV.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { useThree } from '@react-three/fiber';\nimport type { ThreeElements } from '@react-three/fiber';\nimport type { ReactNode } from 'react';\nimport { useEffect, useMemo } from 'react';\nimport * as THREE from 'three';\nimport type {\n PairedSplatEnvironmentConfig,\n ScenarioMaterialConfig,\n SceneConfig,\n SplatCollisionProxyConfig,\n SplatEnvironmentMetadata,\n SplatEnvironmentMetadataInput,\n SplatFormat,\n SplatRendererKind,\n SplatSceneInput,\n ScenarioLightingPreset,\n ScenarioLightingProps,\n SplatEnvironmentProps,\n VisualScenarioConfig,\n VisualScenarioEffectsProps,\n} from '../types';\n\nconst DEFAULT_BACKGROUND = '#181a1f';\n\nexport function ScenarioLighting({\n preset = 'studio',\n castShadow = true,\n intensity = 1,\n}: ScenarioLightingProps) {\n if (preset === 'warehouse') {\n return (\n <>\n <ambientLight intensity={0.18 * intensity} />\n <directionalLight\n position={[3.5, -2, 5]}\n intensity={2.2 * intensity}\n castShadow={castShadow}\n />\n <directionalLight position={[-2, 1.5, 2.5]} intensity={0.25 * intensity} />\n </>\n );\n }\n\n if (preset === 'low-light') {\n return (\n <>\n <ambientLight intensity={0.08 * intensity} />\n <directionalLight\n position={[2, -2, 3]}\n intensity={0.75 * intensity}\n castShadow={castShadow}\n />\n <pointLight position={[-0.5, -0.8, 1.3]} intensity={0.6 * intensity} />\n </>\n );\n }\n\n if (preset === 'splat') {\n return (\n <>\n <ambientLight intensity={0.42 * intensity} />\n <directionalLight\n position={[1.8, -2.4, 3.5]}\n intensity={1.2 * intensity}\n castShadow={castShadow}\n />\n <pointLight position={[0.4, 0.2, 1.4]} intensity={0.35 * intensity} />\n </>\n );\n }\n\n return (\n <>\n <ambientLight intensity={0.35 * intensity} />\n <directionalLight\n position={[2.5, -3, 4]}\n intensity={1.6 * intensity}\n castShadow={castShadow}\n />\n </>\n );\n}\n\nexport function getScenarioBackground(\n preset: ScenarioLightingPreset | undefined,\n fallback = DEFAULT_BACKGROUND\n) {\n if (preset === 'warehouse') return '#20242b';\n if (preset === 'low-light') return '#0f1115';\n if (preset === 'splat') return '#1b1f24';\n return fallback;\n}\n\nexport function getScenarioCameraPosition(\n basePosition: readonly [number, number, number],\n scenario?: Pick<VisualScenarioConfig, 'camera'>\n): [number, number, number] {\n const [x, y, z] = basePosition;\n const jitter = scenario?.camera?.jitter ?? 0;\n\n return [\n Number((x + jitter * 0.6).toFixed(3)),\n Number((y - jitter * 0.4).toFixed(3)),\n Number((z + jitter * 0.25).toFixed(3)),\n ];\n}\n\nexport function VisualScenarioEffects(props: VisualScenarioEffectsProps) {\n useVisualScenarioEffects(props);\n return null;\n}\n\nexport function useVisualScenarioEffects({\n scenario,\n enabled = true,\n applyBackground = true,\n applyFog = true,\n applyRenderer = true,\n applyMaterials = true,\n background,\n fogNear,\n fogFar,\n materialFilter,\n}: VisualScenarioEffectsProps) {\n const { gl, scene, invalidate } = useThree();\n\n useEffect(() => {\n if (!enabled || !scenario) {\n return undefined;\n }\n\n const previousExposure = gl.toneMappingExposure;\n const previousBackground = scene.background;\n const previousFog = scene.fog;\n const materialSnapshots = new Map<\n THREE.Material,\n {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n }\n >();\n\n if (applyRenderer) {\n gl.toneMappingExposure = scenario.camera?.exposure ?? 1;\n }\n\n if (applyBackground) {\n scene.background = new THREE.Color(\n background ?? getScenarioBackground(scenario.lighting)\n );\n }\n\n if (applyFog) {\n scene.fog = createScenarioFog(scenario, background, fogNear, fogFar);\n }\n\n if (applyMaterials && scenario.materials) {\n applyScenarioMaterials(scene, scenario, materialSnapshots, materialFilter);\n }\n\n invalidate();\n\n return () => {\n gl.toneMappingExposure = previousExposure;\n scene.background = previousBackground;\n scene.fog = previousFog;\n\n for (const [material, snapshot] of materialSnapshots) {\n const mutable = getMutableScenarioMaterial(material);\n if (!mutable) continue;\n if (snapshot.color) mutable.color.copy(snapshot.color);\n if (typeof snapshot.roughness === 'number') {\n mutable.roughness = snapshot.roughness;\n }\n if (typeof snapshot.metalness === 'number') {\n mutable.metalness = snapshot.metalness;\n }\n mutable.needsUpdate = true;\n }\n\n invalidate();\n };\n }, [\n applyBackground,\n applyFog,\n applyMaterials,\n applyRenderer,\n background,\n enabled,\n fogFar,\n fogNear,\n gl,\n invalidate,\n materialFilter,\n scenario,\n scene,\n ]);\n}\n\n/**\n * Renderer-agnostic Gaussian splat environment boundary.\n *\n * This component intentionally does not import a specific 3DGS renderer. Pass a\n * Spark/GaussianSplats3D object as `children` once the app chooses a renderer,\n * and pass MuJoCo/MJCF collision proxy visuals via `collisionProxy`.\n */\nexport function SplatEnvironment({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy,\n collisionProxyMetadata,\n children,\n showPlaceholder = true,\n ...groupProps\n}: SplatEnvironmentProps) {\n const metadata = useSplatEnvironment({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy: collisionProxyMetadata,\n });\n const existingUserData =\n typeof groupProps.userData === 'object' && groupProps.userData !== null\n ? groupProps.userData\n : {};\n\n return (\n <group\n {...groupProps}\n userData={{\n ...existingUserData,\n ...metadata.userData,\n }}\n >\n {children}\n {children || !showPlaceholder ? null : <SplatPlaceholder />}\n {collisionProxy}\n </group>\n );\n}\n\nexport function useSplatEnvironment({\n environment,\n scenario,\n renderer,\n src,\n format,\n collisionProxy,\n}: SplatEnvironmentMetadataInput): SplatEnvironmentMetadata {\n const scenarioEnvironment = useMemo(\n () =>\n environment ??\n (scenario\n ? createPairedSplatEnvironment(scenario, { renderer })\n : undefined),\n [environment, renderer, scenario]\n );\n const resolvedSrc = src ?? scenarioEnvironment?.splat.src ?? scenario?.splat?.src;\n const resolvedFormat =\n format ??\n scenarioEnvironment?.splat.format ??\n scenario?.splat?.format ??\n 'spz';\n const resolvedCollisionProxy =\n collisionProxy ??\n scenarioEnvironment?.collisionProxy ??\n scenario?.splat?.collisionProxy ??\n undefined;\n\n return useMemo(\n () => ({\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n userData: createSplatEnvironmentUserData({\n environment: scenarioEnvironment,\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n }),\n }),\n [scenarioEnvironment, resolvedSrc, resolvedFormat, resolvedCollisionProxy]\n );\n}\n\n/**\n * Convert a generic visual scenario splat block into a paired visual/physics\n * environment config. Returns undefined until both the splat asset and MJCF\n * collision proxy are present.\n */\nexport function createPairedSplatEnvironment(\n scenario: Pick<VisualScenarioConfig, 'id' | 'label' | 'environment' | 'splat'>,\n options: {\n id?: string;\n label?: string;\n description?: string;\n renderer?: SplatRendererKind;\n } = {}\n): PairedSplatEnvironmentConfig | undefined {\n const splat = scenario.splat;\n const collisionProxy = splat?.collisionProxy;\n\n if (!splat?.enabled || !splat.src || !collisionProxy?.xmlPath) {\n return undefined;\n }\n\n return {\n id: options.id ?? scenario.id ?? 'splat-environment',\n label: options.label ?? scenario.label ?? 'Gaussian splat environment',\n description:\n options.description ??\n (scenario.environment\n ? `Visual ${scenario.environment} splat paired with MJCF collision proxy.`\n : undefined),\n splat: {\n src: splat.src,\n format: splat.format ?? 'spz',\n renderer: options.renderer,\n },\n collisionProxy: {\n ...collisionProxy,\n xmlPath: collisionProxy.xmlPath,\n },\n };\n}\n\nfunction isPairedSplatEnvironment(input: SplatSceneInput): input is PairedSplatEnvironmentConfig {\n return !!input && 'collisionProxy' in input && 'splat' in input;\n}\n\nfunction sceneRelativePath(sceneConfig: SceneConfig, path: string): string {\n const src = sceneConfig.src;\n if (!src) return path;\n\n const base = src.endsWith('/') ? src : src + '/';\n if (path.startsWith(base)) return path.slice(base.length);\n return path;\n}\n\nfunction uniquePaths(paths: readonly string[]): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n for (const path of paths) {\n if (seen.has(path)) continue;\n seen.add(path);\n result.push(path);\n }\n return result;\n}\n\n/**\n * Compose a MuJoCo scene config with a paired splat collision proxy.\n *\n * This keeps the common hybrid setup declarative:\n * robot XML remains `sceneFile`, the `.spz` remains a visual-only layer, and\n * the paired MJCF collision proxy is added to `environmentFiles`.\n */\nexport function withSplatEnvironment(\n sceneConfig: SceneConfig,\n input: SplatSceneInput,\n options: { renderer?: SplatRendererKind } = {}\n): SceneConfig {\n const environment = isPairedSplatEnvironment(input)\n ? input\n : input\n ? createPairedSplatEnvironment(input, options)\n : undefined;\n const xmlPath = environment?.collisionProxy.xmlPath;\n if (!xmlPath) return sceneConfig;\n\n return {\n ...sceneConfig,\n environmentFiles: uniquePaths([\n ...(sceneConfig.environmentFiles ?? []),\n sceneRelativePath(sceneConfig, xmlPath),\n ]),\n };\n}\n\nexport function createSplatEnvironmentUserData({\n environment,\n src,\n format = 'spz',\n collisionProxy,\n}: {\n environment?: PairedSplatEnvironmentConfig;\n src?: string;\n format?: SplatFormat;\n collisionProxy?: SplatCollisionProxyConfig;\n}) {\n return {\n role: 'splat-environment',\n environmentId: environment?.id,\n environmentLabel: environment?.label,\n splatSrc: src,\n splatFormat: format,\n splatRenderer: environment?.splat.renderer,\n collisionProxyStatus: collisionProxy?.status ?? 'missing',\n collisionProxyXmlPath: collisionProxy?.xmlPath,\n collisionProxyPrimitives: collisionProxy?.primitives ?? [],\n };\n}\n\nexport function createSparkSplatViewerUrl({\n viewerUrl,\n splatSrc,\n}: {\n viewerUrl: string;\n splatSrc: string;\n}) {\n const url = new URL(viewerUrl, 'http://mujoco-react.local');\n url.searchParams.set('splat', splatSrc);\n return viewerUrl.startsWith('http') ? url.toString() : `${url.pathname}${url.search}`;\n}\n\nfunction SplatPlaceholder() {\n return (\n <group>\n <mesh position={[0, 0, 1.2]}>\n <boxGeometry args={[2.4, 2.4, 2.4]} />\n <meshBasicMaterial\n color=\"#8b8b8b\"\n transparent\n opacity={0.06}\n wireframe\n side={THREE.DoubleSide}\n />\n </mesh>\n </group>\n );\n}\n\nfunction createScenarioFog(\n scenario: VisualScenarioConfig,\n background: THREE.ColorRepresentation | undefined,\n fogNear: number | undefined,\n fogFar: number | undefined\n) {\n if (scenario.lighting === 'low-light') {\n return new THREE.Fog(\n background ?? getScenarioBackground(scenario.lighting),\n fogNear ?? 2.5,\n fogFar ?? 9\n );\n }\n\n if (scenario.lighting === 'warehouse') {\n return new THREE.Fog(\n background ?? getScenarioBackground(scenario.lighting),\n fogNear ?? 5,\n fogFar ?? 16\n );\n }\n\n return null;\n}\n\nfunction applyScenarioMaterials(\n scene: THREE.Scene,\n scenario: VisualScenarioConfig,\n snapshots: Map<\n THREE.Material,\n {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n }\n >,\n materialFilter: VisualScenarioEffectsProps['materialFilter']\n) {\n const materials = scenario.materials;\n if (!materials) return;\n\n scene.traverse((object) => {\n if (!(object instanceof THREE.Mesh)) {\n return;\n }\n\n for (const material of normalizeMaterials(object.material)) {\n const mutable = getMutableScenarioMaterial(material);\n if (!mutable) continue;\n if (materialFilter && !materialFilter({ object, material })) continue;\n\n if (!snapshots.has(material)) {\n snapshots.set(material, {\n color: mutable.color.clone(),\n roughness: mutable.roughness,\n metalness: mutable.metalness,\n });\n }\n\n applyScenarioMaterial(mutable, object, scenario, materials);\n }\n });\n}\n\nfunction applyScenarioMaterial(\n material: THREE.MeshStandardMaterial | THREE.MeshPhysicalMaterial,\n object: THREE.Object3D,\n scenario: VisualScenarioConfig,\n materials: ScenarioMaterialConfig\n) {\n const seed = scenario.seed ?? 0;\n const objectKey = `${scenario.id ?? 'scenario'}:${object.name}:${material.name}:${seed}`;\n const variation = hashToUnitInterval(objectKey);\n\n if (materials.randomizeObjectColors) {\n material.color.setHSL(variation, 0.38, 0.42);\n }\n\n if (materials.randomizeTableMaterial) {\n material.roughness = clamp01(\n materials.roughness ?? 0.35 + variation * 0.45\n );\n material.metalness = clamp01(\n materials.metalness ?? variation * 0.12\n );\n }\n\n material.needsUpdate = true;\n}\n\nfunction normalizeMaterials(\n material: THREE.Material | THREE.Material[]\n): THREE.Material[] {\n return Array.isArray(material) ? material : [material];\n}\n\nfunction getMutableScenarioMaterial(\n material: THREE.Material\n): THREE.MeshStandardMaterial | THREE.MeshPhysicalMaterial | null {\n if (\n material instanceof THREE.MeshStandardMaterial ||\n material instanceof THREE.MeshPhysicalMaterial\n ) {\n return material;\n }\n\n return null;\n}\n\nfunction hashToUnitInterval(value: string) {\n let hash = 2166136261;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 16777619);\n }\n return (hash >>> 0) / 4294967295;\n}\n\nfunction clamp01(value: number) {\n return Math.max(0, Math.min(1, value));\n}\n\nexport type SplatCollisionProxy = ReactNode | ThreeElements['group'];\n"]}
|