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/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"]}