mujoco-react 9.3.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 +139 -15
- package/dist/{chunk-T3GVZJ4F.js → chunk-VDSEPZYQ.js} +107 -32
- package/dist/chunk-VDSEPZYQ.js.map +1 -0
- package/dist/index.d.ts +119 -6
- package/dist/index.js +489 -10
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +1 -1
- package/dist/{types-oxbxOkAx.d.ts → types-BuJ4boaq.d.ts} +41 -3
- package/dist/vite.js +8 -4
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SplatCollisionProxyPreview.tsx +350 -0
- package/src/components/VisualScenario.tsx +140 -41
- package/src/core/MujocoSimProvider.tsx +1 -1
- package/src/hooks/useMountedCameraSequenceRecorder.ts +54 -6
- package/src/index.ts +31 -0
- package/src/rendering/cameraFrameSource.ts +372 -0
- package/src/types.ts +44 -2
- package/src/vite.ts +9 -4
- package/dist/chunk-T3GVZJ4F.js.map +0 -1
|
@@ -56,6 +56,27 @@ export interface ResolvedMountedCameraFrameSource {
|
|
|
56
56
|
source: MountedCameraFrameCaptureSource;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export const MountedCameraFrameSourceSuggestionMatch = {
|
|
60
|
+
Direct: 'direct',
|
|
61
|
+
Alias: 'alias',
|
|
62
|
+
Normalized: 'normalized',
|
|
63
|
+
Prefix: 'prefix',
|
|
64
|
+
Suffix: 'suffix',
|
|
65
|
+
Contains: 'contains',
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
68
|
+
export type MountedCameraFrameSourceSuggestionMatch =
|
|
69
|
+
(typeof MountedCameraFrameSourceSuggestionMatch)[keyof typeof MountedCameraFrameSourceSuggestionMatch];
|
|
70
|
+
|
|
71
|
+
export interface MountedCameraFrameSourceSuggestion {
|
|
72
|
+
key: string;
|
|
73
|
+
selector: CameraFrameMountSelector;
|
|
74
|
+
source: MountedCameraFrameCaptureSource;
|
|
75
|
+
resourceName: string;
|
|
76
|
+
resourceKind: MountedCameraFrameCaptureSource['kind'];
|
|
77
|
+
match: MountedCameraFrameSourceSuggestionMatch;
|
|
78
|
+
}
|
|
79
|
+
|
|
59
80
|
export type MountedCameraFrameSequenceDefaults = Omit<
|
|
60
81
|
CameraFrameSequenceCamera,
|
|
61
82
|
'key' | 'cameraName' | 'siteName' | 'bodyName' | 'source'
|
|
@@ -123,6 +144,57 @@ export interface MountedCameraFrameSequenceRecordResult
|
|
|
123
144
|
readiness: MountedCameraFrameSequenceReadiness;
|
|
124
145
|
}
|
|
125
146
|
|
|
147
|
+
export const MountedCameraFrameSequenceManifestStatus = {
|
|
148
|
+
Complete: 'complete',
|
|
149
|
+
Partial: 'partial',
|
|
150
|
+
Missing: 'missing',
|
|
151
|
+
} as const;
|
|
152
|
+
|
|
153
|
+
export type MountedCameraFrameSequenceManifestStatus =
|
|
154
|
+
(typeof MountedCameraFrameSequenceManifestStatus)[keyof typeof MountedCameraFrameSequenceManifestStatus];
|
|
155
|
+
|
|
156
|
+
export interface MountedCameraFrameSequenceStreamSummary {
|
|
157
|
+
key: string;
|
|
158
|
+
ready: boolean;
|
|
159
|
+
complete: boolean;
|
|
160
|
+
status: MountedCameraFrameSequenceManifestStatus;
|
|
161
|
+
source?: CameraFrameCaptureSource;
|
|
162
|
+
selector?: CameraFrameMountSelector;
|
|
163
|
+
target?: string;
|
|
164
|
+
width?: number;
|
|
165
|
+
height?: number;
|
|
166
|
+
expectedFrameCount: number;
|
|
167
|
+
recordedFrameCount: number;
|
|
168
|
+
missingFrameCount: number;
|
|
169
|
+
firstFrameIndex: number | null;
|
|
170
|
+
lastFrameIndex: number | null;
|
|
171
|
+
firstTimestamp: number | null;
|
|
172
|
+
lastTimestamp: number | null;
|
|
173
|
+
message: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface CreateMountedCameraFrameSequenceManifestOptions {
|
|
177
|
+
expectedFrameCount?: number;
|
|
178
|
+
cameraKeys?: readonly string[];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface MountedCameraFrameSequenceManifest {
|
|
182
|
+
schema: 'mujoco-react/mounted-camera-frame-sequence-manifest@1';
|
|
183
|
+
ready: boolean;
|
|
184
|
+
complete: boolean;
|
|
185
|
+
status: MountedCameraFrameSequenceManifestStatus;
|
|
186
|
+
cameraKeys: string[];
|
|
187
|
+
resolvedKeys: string[];
|
|
188
|
+
missingKeys: string[];
|
|
189
|
+
expectedFrameCount: number;
|
|
190
|
+
recordedFrameCount: number;
|
|
191
|
+
missingFrameCount: number;
|
|
192
|
+
streamSummaries: Record<string, MountedCameraFrameSequenceStreamSummary>;
|
|
193
|
+
streams: MountedCameraFrameSequenceStreamSummary[];
|
|
194
|
+
readiness: MountedCameraFrameSequenceReadiness;
|
|
195
|
+
message: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
126
198
|
export type MountedCameraFrameSequenceRecorderTarget = Pick<
|
|
127
199
|
MujocoSimAPI,
|
|
128
200
|
'getCameras' | 'getSites' | 'getBodies' | 'recordCameraSequence'
|
|
@@ -147,6 +219,16 @@ function createNameSet(
|
|
|
147
219
|
);
|
|
148
220
|
}
|
|
149
221
|
|
|
222
|
+
function createResourceNames(
|
|
223
|
+
resources:
|
|
224
|
+
| readonly (string | NamedCameraFrameResource | null | undefined)[]
|
|
225
|
+
| undefined
|
|
226
|
+
) {
|
|
227
|
+
return (resources ?? [])
|
|
228
|
+
.map((resource) => getResourceName(resource))
|
|
229
|
+
.filter((name): name is string => Boolean(name));
|
|
230
|
+
}
|
|
231
|
+
|
|
150
232
|
function normalizeAliasCandidates(
|
|
151
233
|
value: CameraFrameMountSelector | readonly CameraFrameMountSelector[] | undefined
|
|
152
234
|
) {
|
|
@@ -160,6 +242,35 @@ function countMountedSelectors(selector: CameraFrameMountSelector) {
|
|
|
160
242
|
Number(Boolean(selector.bodyName));
|
|
161
243
|
}
|
|
162
244
|
|
|
245
|
+
function normalizeCameraSourceName(value: string) {
|
|
246
|
+
return value
|
|
247
|
+
.trim()
|
|
248
|
+
.toLowerCase()
|
|
249
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
250
|
+
.replace(/^_+|_+$/g, '');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function createCameraSourceKeyVariants(key: string) {
|
|
254
|
+
const candidates = [
|
|
255
|
+
key,
|
|
256
|
+
key.startsWith('observation.images.')
|
|
257
|
+
? key.slice('observation.images.'.length)
|
|
258
|
+
: '',
|
|
259
|
+
key.includes('.') ? key.split('.').at(-1) ?? '' : '',
|
|
260
|
+
key.includes('/') ? key.split('/').at(-1) ?? '' : '',
|
|
261
|
+
];
|
|
262
|
+
return candidates
|
|
263
|
+
.map((candidate) => candidate.trim())
|
|
264
|
+
.filter((candidate, index, items) => candidate && items.indexOf(candidate) === index);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function getSelectorKey(selector: CameraFrameMountSelector) {
|
|
268
|
+
if (selector.cameraName) return `camera:${selector.cameraName}`;
|
|
269
|
+
if (selector.siteName) return `site:${selector.siteName}`;
|
|
270
|
+
if (selector.bodyName) return `body:${selector.bodyName}`;
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
|
|
163
274
|
export function getMountedCameraFrameCaptureSource(
|
|
164
275
|
selector: CameraFrameMountSelector
|
|
165
276
|
): MountedCameraFrameCaptureSource | null {
|
|
@@ -197,6 +308,66 @@ export function getCameraFrameCaptureSourceTarget(
|
|
|
197
308
|
return 'fallback camera';
|
|
198
309
|
}
|
|
199
310
|
|
|
311
|
+
function createMountedCameraFrameSourceSuggestion(
|
|
312
|
+
key: string,
|
|
313
|
+
selector: CameraFrameMountSelector,
|
|
314
|
+
resourceName: string,
|
|
315
|
+
match: MountedCameraFrameSourceSuggestionMatch
|
|
316
|
+
): MountedCameraFrameSourceSuggestion | null {
|
|
317
|
+
const source = getMountedCameraFrameCaptureSource(selector);
|
|
318
|
+
if (!source) return null;
|
|
319
|
+
return {
|
|
320
|
+
key,
|
|
321
|
+
selector,
|
|
322
|
+
source,
|
|
323
|
+
resourceName,
|
|
324
|
+
resourceKind: source.kind,
|
|
325
|
+
match,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function addMountedCameraFrameSourceSuggestion(
|
|
330
|
+
suggestions: MountedCameraFrameSourceSuggestion[],
|
|
331
|
+
seen: Set<string>,
|
|
332
|
+
suggestion: MountedCameraFrameSourceSuggestion | null
|
|
333
|
+
) {
|
|
334
|
+
if (!suggestion) return;
|
|
335
|
+
const selectorKey = getSelectorKey(suggestion.selector);
|
|
336
|
+
if (!selectorKey || seen.has(selectorKey)) return;
|
|
337
|
+
seen.add(selectorKey);
|
|
338
|
+
suggestions.push(suggestion);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function getCameraFrameResourceMatch(
|
|
342
|
+
key: string,
|
|
343
|
+
resourceName: string
|
|
344
|
+
): MountedCameraFrameSourceSuggestionMatch | null {
|
|
345
|
+
if (resourceName === key) return MountedCameraFrameSourceSuggestionMatch.Direct;
|
|
346
|
+
|
|
347
|
+
const normalizedResource = normalizeCameraSourceName(resourceName);
|
|
348
|
+
if (!normalizedResource) return null;
|
|
349
|
+
|
|
350
|
+
for (const variant of createCameraSourceKeyVariants(key)) {
|
|
351
|
+
if (resourceName === variant) return MountedCameraFrameSourceSuggestionMatch.Direct;
|
|
352
|
+
|
|
353
|
+
const normalizedKey = normalizeCameraSourceName(variant);
|
|
354
|
+
if (!normalizedKey) continue;
|
|
355
|
+
if (normalizedResource === normalizedKey) {
|
|
356
|
+
return MountedCameraFrameSourceSuggestionMatch.Normalized;
|
|
357
|
+
}
|
|
358
|
+
if (normalizedResource.startsWith(`${normalizedKey}_`)) {
|
|
359
|
+
return MountedCameraFrameSourceSuggestionMatch.Prefix;
|
|
360
|
+
}
|
|
361
|
+
if (normalizedResource.endsWith(`_${normalizedKey}`)) {
|
|
362
|
+
return MountedCameraFrameSourceSuggestionMatch.Suffix;
|
|
363
|
+
}
|
|
364
|
+
if (normalizedResource.includes(`_${normalizedKey}_`)) {
|
|
365
|
+
return MountedCameraFrameSourceSuggestionMatch.Contains;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
200
371
|
function isSelectorMounted(
|
|
201
372
|
selector: CameraFrameMountSelector,
|
|
202
373
|
cameraNames: Set<string>,
|
|
@@ -211,6 +382,82 @@ function isSelectorMounted(
|
|
|
211
382
|
);
|
|
212
383
|
}
|
|
213
384
|
|
|
385
|
+
export function createMountedCameraFrameSourceSuggestions(
|
|
386
|
+
key: string,
|
|
387
|
+
options: ResolveMountedCameraFrameSourceOptions
|
|
388
|
+
): MountedCameraFrameSourceSuggestion[] {
|
|
389
|
+
const cameraNames = createNameSet(options.cameras);
|
|
390
|
+
const siteNames = createNameSet(options.sites);
|
|
391
|
+
const bodyNames = createNameSet(options.bodies);
|
|
392
|
+
const suggestions: MountedCameraFrameSourceSuggestion[] = [];
|
|
393
|
+
const seen = new Set<string>();
|
|
394
|
+
|
|
395
|
+
for (const selector of normalizeAliasCandidates(options.aliases?.[key])) {
|
|
396
|
+
if (!isSelectorMounted(selector, cameraNames, siteNames, bodyNames)) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const source = getMountedCameraFrameCaptureSource(selector);
|
|
400
|
+
if (!source) continue;
|
|
401
|
+
addMountedCameraFrameSourceSuggestion(
|
|
402
|
+
suggestions,
|
|
403
|
+
seen,
|
|
404
|
+
createMountedCameraFrameSourceSuggestion(
|
|
405
|
+
key,
|
|
406
|
+
selector,
|
|
407
|
+
getCameraFrameCaptureSourceTarget(source),
|
|
408
|
+
MountedCameraFrameSourceSuggestionMatch.Alias
|
|
409
|
+
)
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
for (const cameraName of createResourceNames(options.cameras)) {
|
|
414
|
+
const match = getCameraFrameResourceMatch(key, cameraName);
|
|
415
|
+
if (!match) continue;
|
|
416
|
+
addMountedCameraFrameSourceSuggestion(
|
|
417
|
+
suggestions,
|
|
418
|
+
seen,
|
|
419
|
+
createMountedCameraFrameSourceSuggestion(
|
|
420
|
+
key,
|
|
421
|
+
{ cameraName },
|
|
422
|
+
cameraName,
|
|
423
|
+
match
|
|
424
|
+
)
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
for (const siteName of createResourceNames(options.sites)) {
|
|
429
|
+
const match = getCameraFrameResourceMatch(key, siteName);
|
|
430
|
+
if (!match) continue;
|
|
431
|
+
addMountedCameraFrameSourceSuggestion(
|
|
432
|
+
suggestions,
|
|
433
|
+
seen,
|
|
434
|
+
createMountedCameraFrameSourceSuggestion(
|
|
435
|
+
key,
|
|
436
|
+
{ siteName },
|
|
437
|
+
siteName,
|
|
438
|
+
match
|
|
439
|
+
)
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
for (const bodyName of createResourceNames(options.bodies)) {
|
|
444
|
+
const match = getCameraFrameResourceMatch(key, bodyName);
|
|
445
|
+
if (!match) continue;
|
|
446
|
+
addMountedCameraFrameSourceSuggestion(
|
|
447
|
+
suggestions,
|
|
448
|
+
seen,
|
|
449
|
+
createMountedCameraFrameSourceSuggestion(
|
|
450
|
+
key,
|
|
451
|
+
{ bodyName },
|
|
452
|
+
bodyName,
|
|
453
|
+
match
|
|
454
|
+
)
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return suggestions;
|
|
459
|
+
}
|
|
460
|
+
|
|
214
461
|
export function resolveMountedCameraFrameSource(
|
|
215
462
|
key: string,
|
|
216
463
|
options: ResolveMountedCameraFrameSourceOptions
|
|
@@ -235,6 +482,15 @@ export function resolveMountedCameraFrameSource(
|
|
|
235
482
|
return { key, selector, source };
|
|
236
483
|
}
|
|
237
484
|
|
|
485
|
+
const [suggestion] = createMountedCameraFrameSourceSuggestions(key, options);
|
|
486
|
+
if (suggestion) {
|
|
487
|
+
return {
|
|
488
|
+
key,
|
|
489
|
+
selector: suggestion.selector,
|
|
490
|
+
source: suggestion.source,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
238
494
|
if (options.allowAliasFallback) {
|
|
239
495
|
for (const selector of aliasCandidates) {
|
|
240
496
|
const source = getMountedCameraFrameCaptureSource(selector);
|
|
@@ -335,6 +591,122 @@ export function createMountedCameraFrameSequenceReadiness(
|
|
|
335
591
|
};
|
|
336
592
|
}
|
|
337
593
|
|
|
594
|
+
function normalizeFrameCount(frameCount: number | undefined) {
|
|
595
|
+
return Number.isFinite(frameCount) && frameCount !== undefined
|
|
596
|
+
? Math.max(0, Math.floor(frameCount))
|
|
597
|
+
: 0;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function createMountedCameraFrameSequenceManifest(
|
|
601
|
+
result: MountedCameraFrameSequenceRecordResult,
|
|
602
|
+
options: CreateMountedCameraFrameSequenceManifestOptions = {}
|
|
603
|
+
): MountedCameraFrameSequenceManifest {
|
|
604
|
+
const cameraKeys = [
|
|
605
|
+
...(options.cameraKeys ??
|
|
606
|
+
result.readiness.cameraKeys ??
|
|
607
|
+
result.plan.cameraKeys ??
|
|
608
|
+
result.cameraKeys),
|
|
609
|
+
];
|
|
610
|
+
const expectedFrameCount = normalizeFrameCount(
|
|
611
|
+
options.expectedFrameCount ?? result.frameCount
|
|
612
|
+
);
|
|
613
|
+
const recordedFrameCount = normalizeFrameCount(result.frameCount);
|
|
614
|
+
const streamSummaries: Record<string, MountedCameraFrameSequenceStreamSummary> =
|
|
615
|
+
{};
|
|
616
|
+
const streams: MountedCameraFrameSequenceStreamSummary[] = [];
|
|
617
|
+
let missingFrameCount = 0;
|
|
618
|
+
let completeStreamCount = 0;
|
|
619
|
+
let resolvedOrRecordedStreamCount = 0;
|
|
620
|
+
|
|
621
|
+
for (const key of cameraKeys) {
|
|
622
|
+
const summary = result.cameraSummaries[key];
|
|
623
|
+
const readiness = result.readiness.cameras[key];
|
|
624
|
+
const source = summary?.source ?? readiness?.source;
|
|
625
|
+
const ready = readiness?.ready ?? Boolean(summary);
|
|
626
|
+
const recorded = normalizeFrameCount(summary?.frameCount);
|
|
627
|
+
const missing = Math.max(expectedFrameCount - recorded, 0);
|
|
628
|
+
const complete = ready && missing === 0;
|
|
629
|
+
const status = complete
|
|
630
|
+
? MountedCameraFrameSequenceManifestStatus.Complete
|
|
631
|
+
: ready || recorded > 0
|
|
632
|
+
? MountedCameraFrameSequenceManifestStatus.Partial
|
|
633
|
+
: MountedCameraFrameSequenceManifestStatus.Missing;
|
|
634
|
+
const target = source
|
|
635
|
+
? getCameraFrameCaptureSourceTarget(source)
|
|
636
|
+
: readiness?.message
|
|
637
|
+
? undefined
|
|
638
|
+
: 'missing MuJoCo camera';
|
|
639
|
+
const message = complete
|
|
640
|
+
? `Camera stream "${key}" recorded ${recorded} of ${expectedFrameCount} frame${
|
|
641
|
+
expectedFrameCount === 1 ? '' : 's'
|
|
642
|
+
}.`
|
|
643
|
+
: ready || recorded > 0
|
|
644
|
+
? `Camera stream "${key}" recorded ${recorded} of ${expectedFrameCount} frame${
|
|
645
|
+
expectedFrameCount === 1 ? '' : 's'
|
|
646
|
+
}.`
|
|
647
|
+
: readiness?.message ??
|
|
648
|
+
`Camera stream "${key}" did not record any frames.`;
|
|
649
|
+
const stream = {
|
|
650
|
+
key,
|
|
651
|
+
ready,
|
|
652
|
+
complete,
|
|
653
|
+
status,
|
|
654
|
+
source,
|
|
655
|
+
selector: readiness?.selector,
|
|
656
|
+
target,
|
|
657
|
+
width: summary?.width,
|
|
658
|
+
height: summary?.height,
|
|
659
|
+
expectedFrameCount,
|
|
660
|
+
recordedFrameCount: recorded,
|
|
661
|
+
missingFrameCount: missing,
|
|
662
|
+
firstFrameIndex: summary?.firstFrameIndex ?? null,
|
|
663
|
+
lastFrameIndex: summary?.lastFrameIndex ?? null,
|
|
664
|
+
firstTimestamp: summary?.firstTimestamp ?? null,
|
|
665
|
+
lastTimestamp: summary?.lastTimestamp ?? null,
|
|
666
|
+
message,
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
streamSummaries[key] = stream;
|
|
670
|
+
streams.push(stream);
|
|
671
|
+
missingFrameCount += missing;
|
|
672
|
+
if (complete) completeStreamCount += 1;
|
|
673
|
+
if (ready || recorded > 0) resolvedOrRecordedStreamCount += 1;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const complete =
|
|
677
|
+
result.readiness.ready &&
|
|
678
|
+
streams.length === completeStreamCount &&
|
|
679
|
+
missingFrameCount === 0;
|
|
680
|
+
const status = complete
|
|
681
|
+
? MountedCameraFrameSequenceManifestStatus.Complete
|
|
682
|
+
: resolvedOrRecordedStreamCount > 0
|
|
683
|
+
? MountedCameraFrameSequenceManifestStatus.Partial
|
|
684
|
+
: MountedCameraFrameSequenceManifestStatus.Missing;
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
schema: 'mujoco-react/mounted-camera-frame-sequence-manifest@1',
|
|
688
|
+
ready: result.readiness.ready,
|
|
689
|
+
complete,
|
|
690
|
+
status,
|
|
691
|
+
cameraKeys,
|
|
692
|
+
resolvedKeys: [...result.readiness.resolvedKeys],
|
|
693
|
+
missingKeys: [...result.readiness.missingKeys],
|
|
694
|
+
expectedFrameCount,
|
|
695
|
+
recordedFrameCount,
|
|
696
|
+
missingFrameCount,
|
|
697
|
+
streamSummaries,
|
|
698
|
+
streams,
|
|
699
|
+
readiness: result.readiness,
|
|
700
|
+
message: complete
|
|
701
|
+
? `All ${cameraKeys.length} camera stream${
|
|
702
|
+
cameraKeys.length === 1 ? '' : 's'
|
|
703
|
+
} recorded ${expectedFrameCount} frame${
|
|
704
|
+
expectedFrameCount === 1 ? '' : 's'
|
|
705
|
+
}.`
|
|
706
|
+
: `Mounted camera sequence coverage is ${status}.`,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
338
710
|
export function createMountedCameraFrameSequencePlanFromApi(
|
|
339
711
|
api: MountedCameraFrameSequenceRecorderTarget,
|
|
340
712
|
cameraKeys: readonly string[],
|
package/src/types.ts
CHANGED
|
@@ -928,8 +928,8 @@ export interface PairedSplatEnvironmentConfig {
|
|
|
928
928
|
description?: string;
|
|
929
929
|
/** Visual-only Gaussian splat asset. */
|
|
930
930
|
splat: SplatAssetConfig;
|
|
931
|
-
/** MJCF/XML contact geometry paired with the visual splat. */
|
|
932
|
-
collisionProxy
|
|
931
|
+
/** Optional MJCF/XML contact geometry paired with the visual splat. */
|
|
932
|
+
collisionProxy?: SplatCollisionProxyConfig & { xmlPath: string };
|
|
933
933
|
}
|
|
934
934
|
|
|
935
935
|
export const SplatEnvironmentReadinessStatus = {
|
|
@@ -970,6 +970,48 @@ export interface SplatEnvironmentMetadata {
|
|
|
970
970
|
userData: Record<string, unknown>;
|
|
971
971
|
}
|
|
972
972
|
|
|
973
|
+
export interface ResolvedScenarioCameraConfig {
|
|
974
|
+
jitter: number;
|
|
975
|
+
exposure: number;
|
|
976
|
+
noise: number;
|
|
977
|
+
blur: number;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export interface ResolvedScenarioMaterialConfig {
|
|
981
|
+
randomizeObjectColors: boolean;
|
|
982
|
+
randomizeTableMaterial: boolean;
|
|
983
|
+
roughness?: number;
|
|
984
|
+
metalness?: number;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
export interface VisualScenarioExecutionContext {
|
|
988
|
+
scenarioId: string;
|
|
989
|
+
scenarioLabel: string;
|
|
990
|
+
variantId?: string;
|
|
991
|
+
seed: number;
|
|
992
|
+
lighting: ScenarioLightingPreset;
|
|
993
|
+
environment?: string;
|
|
994
|
+
camera: ResolvedScenarioCameraConfig;
|
|
995
|
+
materials: ResolvedScenarioMaterialConfig;
|
|
996
|
+
splatEnabled: boolean;
|
|
997
|
+
splatSrc?: string;
|
|
998
|
+
splatFormat: SplatFormat;
|
|
999
|
+
splatRenderer?: SplatRendererKind;
|
|
1000
|
+
collisionProxyXmlPath?: string;
|
|
1001
|
+
collisionProxyStatus?: SplatCollisionProxyConfig['status'];
|
|
1002
|
+
collisionProxyPrimitives: SplatCollisionPrimitive[];
|
|
1003
|
+
readiness: SplatEnvironmentReadiness;
|
|
1004
|
+
transformSource: 'visualScenario.camera';
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
export interface VisualScenarioExecutionContextInput {
|
|
1008
|
+
scenario?: VisualScenarioConfig;
|
|
1009
|
+
environment?: PairedSplatEnvironmentConfig;
|
|
1010
|
+
renderer?: SplatRendererKind;
|
|
1011
|
+
variantId?: string;
|
|
1012
|
+
enabled?: boolean;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
973
1015
|
export type SplatSceneInput =
|
|
974
1016
|
| PairedSplatEnvironmentConfig
|
|
975
1017
|
| VisualScenarioConfig
|
package/src/vite.ts
CHANGED
|
@@ -158,7 +158,7 @@ async function scanModel(
|
|
|
158
158
|
seen: Set<string>,
|
|
159
159
|
names: Record<RegisterKey, Set<string>>
|
|
160
160
|
) {
|
|
161
|
-
const normalized = path.
|
|
161
|
+
const normalized = path.resolve(filePath);
|
|
162
162
|
if (seen.has(normalized)) return;
|
|
163
163
|
seen.add(normalized);
|
|
164
164
|
|
|
@@ -174,7 +174,7 @@ async function scanModel(
|
|
|
174
174
|
|
|
175
175
|
for (const includePath of collectIncludePaths(xml)) {
|
|
176
176
|
const next = path.resolve(path.dirname(normalized), includePath);
|
|
177
|
-
if (next
|
|
177
|
+
if (isPathInsideRoot(next, root)) await scanModel(next, root, seen, names);
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
|
|
@@ -346,7 +346,7 @@ function shouldInjectRegisterImport(id: string, root: string, generatedRegister:
|
|
|
346
346
|
if (file.includes(`${path.sep}node_modules${path.sep}`)) return false;
|
|
347
347
|
const absolute = path.resolve(file);
|
|
348
348
|
if (absolute === generatedRegister) return false;
|
|
349
|
-
return absolute
|
|
349
|
+
return isPathInsideRoot(absolute, root);
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
function renderGeneratedImport(id: string, generatedRegister: string): string {
|
|
@@ -395,5 +395,10 @@ function shouldRegenerate(file: string, watchedFiles: string[], models: readonly
|
|
|
395
395
|
if (watchedFiles.includes(absolute)) return true;
|
|
396
396
|
if (!MODEL_EXTENSIONS.has(path.extname(absolute).toLowerCase())) return false;
|
|
397
397
|
const modelDirs = models.map((model) => path.dirname(path.resolve(root, model.file)));
|
|
398
|
-
return modelDirs.some((dir) => absolute
|
|
398
|
+
return modelDirs.some((dir) => isPathInsideRoot(absolute, dir));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function isPathInsideRoot(filePath: string, root: string): boolean {
|
|
402
|
+
const relative = path.relative(path.resolve(root), path.resolve(filePath));
|
|
403
|
+
return relative === '' || (relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
399
404
|
}
|