@found-in-space/skykit 0.2.0-dev.20260527.0 → 0.2.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 +40 -35
- package/examples/xr-free-roam/xr-free-roam.js +31 -57
- package/package.json +11 -21
- package/src/__tests__/skykit-browser.test.js +184 -40
- package/src/__tests__/skykit-xr.test.js +56 -0
- package/src/__tests__/skykit.test.js +90 -503
- package/src/actions.js +0 -8
- package/src/browser.d.ts +2 -19
- package/src/browser.js +18 -31
- package/src/embed.js +22 -2
- package/src/hr-diagram.js +3 -1
- package/src/index.d.ts +10 -78
- package/src/index.js +6 -1
- package/src/plugins.js +0 -730
- package/src/utils.js +1 -0
- package/src/xr/plugins.js +74 -0
- package/src/xr.d.ts +18 -0
- package/src/xr.js +1 -0
- package/src/browser-journey.d.ts +0 -8
- package/src/browser-journey.js +0 -240
- package/src/story.d.ts +0 -57
- package/src/story.js +0 -396
package/src/plugins.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
createJourneyController,
|
|
5
|
-
createTimedJourneyEvaluator,
|
|
6
|
-
} from '@found-in-space/journey';
|
|
7
|
-
|
|
8
3
|
import {
|
|
9
4
|
combineStrategies,
|
|
10
5
|
createLookaheadStrategy,
|
|
@@ -16,7 +11,6 @@ import {
|
|
|
16
11
|
|
|
17
12
|
import {
|
|
18
13
|
IDENTITY_QUATERNION as SPATIAL_IDENTITY_QUATERNION,
|
|
19
|
-
createOrbitTransferRoute,
|
|
20
14
|
createSpatialPoseTransition,
|
|
21
15
|
createSpatialNavigationAutomation,
|
|
22
16
|
resolveSpatialLookAt,
|
|
@@ -408,600 +402,6 @@ export function createSkykitNavigationPlugin(options = {}) {
|
|
|
408
402
|
}
|
|
409
403
|
}
|
|
410
404
|
|
|
411
|
-
/**
|
|
412
|
-
* @param {import('./index.d.ts').SkykitJourneyPluginOptions} [options]
|
|
413
|
-
* @returns {import('./index.d.ts').SkykitJourneyPlugin}
|
|
414
|
-
*/
|
|
415
|
-
export function createSkykitJourneyPlugin(options = {}) {
|
|
416
|
-
const id = options.id ?? 'journey';
|
|
417
|
-
const controller = options.controller
|
|
418
|
-
?? (options.journey
|
|
419
|
-
? createJourneyController({ graph: options.journey })
|
|
420
|
-
: (options.graph || options.scenes ? createJourneyController(options) : null));
|
|
421
|
-
const evaluator = options.evaluator
|
|
422
|
-
?? (options.timedJourney ? createTimedJourneyEvaluator(options.timedJourney, options.evaluatorOptions) : null);
|
|
423
|
-
let disposed = false;
|
|
424
|
-
let playing = options.autoPlay === true;
|
|
425
|
-
let currentTimeSecs = Math.max(0, finiteNumber(options.startTimeSecs, 0));
|
|
426
|
-
let lastCueId = /** @type {string | null} */ (null);
|
|
427
|
-
let initialSceneApplied = false;
|
|
428
|
-
let timedPreloadHintsEmitted = false;
|
|
429
|
-
/** @type {import('./index.d.ts').SkykitThreePluginContext | null} */
|
|
430
|
-
let pluginContext = null;
|
|
431
|
-
const resolvedJourneyOrbits = new Map();
|
|
432
|
-
/** @type {(() => void) | null} */
|
|
433
|
-
let unsubscribeController = null;
|
|
434
|
-
/** @type {Promise<unknown>} */
|
|
435
|
-
let pendingSceneApplication = Promise.resolve(null);
|
|
436
|
-
|
|
437
|
-
/** @type {SkykitThreePart} */
|
|
438
|
-
const part = {
|
|
439
|
-
id,
|
|
440
|
-
priority: options.priority,
|
|
441
|
-
attach(context) {
|
|
442
|
-
pluginContext = context;
|
|
443
|
-
if (controller) {
|
|
444
|
-
unsubscribeController = controller.subscribe((event) => {
|
|
445
|
-
queueSceneApplication(event.spec, context, event);
|
|
446
|
-
});
|
|
447
|
-
applyInitialScene(context);
|
|
448
|
-
}
|
|
449
|
-
emitTimedPreloadHints(context, 'attach');
|
|
450
|
-
},
|
|
451
|
-
update(frame) {
|
|
452
|
-
if (disposed || !evaluator || !playing) return;
|
|
453
|
-
currentTimeSecs = Math.min(
|
|
454
|
-
evaluator.durationSecs,
|
|
455
|
-
currentTimeSecs + Math.max(0, finiteNumber(frame.deltaSeconds, 0)),
|
|
456
|
-
);
|
|
457
|
-
applyTimedFrame(evaluator.evaluate(currentTimeSecs), frame);
|
|
458
|
-
if (currentTimeSecs >= evaluator.durationSecs) {
|
|
459
|
-
playing = options.loop === true;
|
|
460
|
-
currentTimeSecs = playing ? 0 : evaluator.durationSecs;
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
detach() {
|
|
464
|
-
unsubscribeController?.();
|
|
465
|
-
unsubscribeController = null;
|
|
466
|
-
pluginContext = null;
|
|
467
|
-
},
|
|
468
|
-
dispose() {
|
|
469
|
-
disposed = true;
|
|
470
|
-
this.detach?.();
|
|
471
|
-
if (options.disposeController !== false) controller?.dispose?.();
|
|
472
|
-
},
|
|
473
|
-
getSnapshot,
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
return {
|
|
477
|
-
id,
|
|
478
|
-
setup(context) {
|
|
479
|
-
const threeContext = /** @type {import('./index.d.ts').SkykitThreePluginContext} */ (context);
|
|
480
|
-
const removePart = threeContext.addPart(part);
|
|
481
|
-
const unregisterActions = registerJourneyActions(threeContext);
|
|
482
|
-
return () => {
|
|
483
|
-
unregisterActions();
|
|
484
|
-
removePart();
|
|
485
|
-
};
|
|
486
|
-
},
|
|
487
|
-
goTo,
|
|
488
|
-
next,
|
|
489
|
-
previous,
|
|
490
|
-
seek,
|
|
491
|
-
play,
|
|
492
|
-
pause,
|
|
493
|
-
getSnapshot,
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
/** @param {unknown} payload */
|
|
497
|
-
async function goTo(payload) {
|
|
498
|
-
await pendingSceneApplication;
|
|
499
|
-
const sceneId = resolveSceneId(payload);
|
|
500
|
-
const spec = sceneId && controller ? controller.goTo(sceneId, { source: SKYKIT_ACTIONS.journey.goToChapter }) : null;
|
|
501
|
-
await pendingSceneApplication;
|
|
502
|
-
return spec;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
async function next() {
|
|
506
|
-
await pendingSceneApplication;
|
|
507
|
-
const spec = controller?.next({ source: SKYKIT_ACTIONS.journey.next }) ?? null;
|
|
508
|
-
await pendingSceneApplication;
|
|
509
|
-
return spec;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
async function previous() {
|
|
513
|
-
await pendingSceneApplication;
|
|
514
|
-
const spec = controller?.previous({ source: SKYKIT_ACTIONS.journey.previous }) ?? null;
|
|
515
|
-
await pendingSceneApplication;
|
|
516
|
-
return spec;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/** @param {unknown} payload */
|
|
520
|
-
function seek(payload) {
|
|
521
|
-
currentTimeSecs = clampTime(resolveTimeSecs(payload), evaluator?.durationSecs ?? Number.POSITIVE_INFINITY);
|
|
522
|
-
if (evaluator && pluginContext) applyTimedFrame(evaluator.evaluate(currentTimeSecs), { viewer: pluginContext.viewer, view: pluginContext.getViewState() });
|
|
523
|
-
if (pluginContext) emitTimedPreloadHints(pluginContext, 'seek');
|
|
524
|
-
return currentTimeSecs;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/** @param {unknown} [payload] */
|
|
528
|
-
function play(payload) {
|
|
529
|
-
if (payload && typeof payload === 'object' && 'timeSecs' in payload) {
|
|
530
|
-
currentTimeSecs = clampTime(resolveTimeSecs(payload), evaluator?.durationSecs ?? Number.POSITIVE_INFINITY);
|
|
531
|
-
}
|
|
532
|
-
playing = true;
|
|
533
|
-
if (pluginContext) emitTimedPreloadHints(pluginContext, 'play');
|
|
534
|
-
return currentTimeSecs;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function pause() {
|
|
538
|
-
playing = false;
|
|
539
|
-
return currentTimeSecs;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/** @param {import('./index.d.ts').SkykitThreePluginContext} context */
|
|
543
|
-
function registerJourneyActions(context) {
|
|
544
|
-
const unregisters = [
|
|
545
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.goToChapter, ({ payload }) => goTo(payload), { label: 'Go to journey chapter' }),
|
|
546
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.next, () => next(), {
|
|
547
|
-
label: 'Next journey chapter',
|
|
548
|
-
}),
|
|
549
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.previous, () => previous(), {
|
|
550
|
-
label: 'Previous journey chapter',
|
|
551
|
-
}),
|
|
552
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.seek, ({ payload }) => seek(payload), { label: 'Seek journey time' }),
|
|
553
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.play, ({ payload }) => play(payload), { label: 'Play journey' }),
|
|
554
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.pause, () => pause(), { label: 'Pause journey' }),
|
|
555
|
-
];
|
|
556
|
-
return () => {
|
|
557
|
-
for (const unregister of unregisters.reverse()) unregister();
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/** @param {import('./index.d.ts').SkykitThreePluginContext} context */
|
|
562
|
-
function applyInitialScene(context) {
|
|
563
|
-
if (!controller || initialSceneApplied) return;
|
|
564
|
-
const snapshot = controller.getSnapshot();
|
|
565
|
-
const sceneId = snapshot.activeSceneId;
|
|
566
|
-
if (!sceneId) {
|
|
567
|
-
initialSceneApplied = true;
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
initialSceneApplied = true;
|
|
571
|
-
const spec = controller.graph.resolveSceneSpec(sceneId, { fromSceneId: snapshot.previousSceneId });
|
|
572
|
-
queueSceneApplication(spec, context, {
|
|
573
|
-
type: 'journey/initial',
|
|
574
|
-
sceneId,
|
|
575
|
-
previousSceneId: snapshot.previousSceneId,
|
|
576
|
-
source: 'attach',
|
|
577
|
-
spec,
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* @param {unknown} spec
|
|
583
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
584
|
-
* @param {unknown} event
|
|
585
|
-
*/
|
|
586
|
-
function queueSceneApplication(spec, context, event) {
|
|
587
|
-
pendingSceneApplication = Promise.resolve(applySceneSpec(spec, context, event)).catch((error) => {
|
|
588
|
-
context.emit?.({
|
|
589
|
-
type: 'journey/scene/error',
|
|
590
|
-
pluginId: id,
|
|
591
|
-
message: 'Failed to apply journey scene.',
|
|
592
|
-
error,
|
|
593
|
-
event,
|
|
594
|
-
});
|
|
595
|
-
return null;
|
|
596
|
-
});
|
|
597
|
-
return pendingSceneApplication;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
602
|
-
* @param {string} source
|
|
603
|
-
*/
|
|
604
|
-
function emitTimedPreloadHints(context, source) {
|
|
605
|
-
if (!evaluator || timedPreloadHintsEmitted) return;
|
|
606
|
-
const preloadHints = typeof evaluator.getPreloadHints === 'function'
|
|
607
|
-
? evaluator.getPreloadHints()
|
|
608
|
-
: evaluator.evaluate(currentTimeSecs).preloadHints;
|
|
609
|
-
timedPreloadHintsEmitted = true;
|
|
610
|
-
if (preloadHints.length > 0) {
|
|
611
|
-
options.onPreloadHints?.(preloadHints, {
|
|
612
|
-
type: 'journey/timed-preload',
|
|
613
|
-
journeyId: evaluator.journey.id,
|
|
614
|
-
source,
|
|
615
|
-
}, context);
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* @param {unknown} spec
|
|
621
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
622
|
-
* @param {unknown} event
|
|
623
|
-
*/
|
|
624
|
-
async function applySceneSpec(spec, context, event) {
|
|
625
|
-
const scene = /** @type {Record<string, unknown> | null} */ (spec && typeof spec === 'object' ? spec : null);
|
|
626
|
-
options.onScene?.(scene, context, event);
|
|
627
|
-
if (!scene) return;
|
|
628
|
-
const onArrive = () => {
|
|
629
|
-
void notifySceneArrive(scene, context, event);
|
|
630
|
-
};
|
|
631
|
-
let arrivalDeferred = false;
|
|
632
|
-
if (scene.view && typeof scene.view === 'object') {
|
|
633
|
-
context.requestViewState(/** @type {Partial<import('./index.d.ts').SkykitViewState>} */ (scene.view), id);
|
|
634
|
-
}
|
|
635
|
-
const navigation = /** @type {Record<string, unknown> | null} */ (scene.navigation && typeof scene.navigation === 'object' ? scene.navigation : null);
|
|
636
|
-
if (isOrbitCameraScene(scene)) {
|
|
637
|
-
arrivalDeferred = await applyOrbitCameraScene(scene, context, event, onArrive);
|
|
638
|
-
} else if (navigation?.transitionTo) {
|
|
639
|
-
const travel = normalizeJourneySceneTravel(scene.travel);
|
|
640
|
-
const explicitRoutePoints = await resolveJourneyRoutePoints(scene, travel, context);
|
|
641
|
-
const results = explicitRoutePoints.length >= 2
|
|
642
|
-
? await context.actions.invoke(SKYKIT_ACTIONS.navigation.flyPolyline, {
|
|
643
|
-
points: explicitRoutePoints,
|
|
644
|
-
durationSecs: travel.durationSecs,
|
|
645
|
-
arrivalThreshold: travel.arrivalThreshold,
|
|
646
|
-
onArrive: () => {
|
|
647
|
-
void context.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
|
|
648
|
-
...createRouteArrivalTransitionPayload(navigation.transitionTo),
|
|
649
|
-
onArrive,
|
|
650
|
-
}, {
|
|
651
|
-
source: id,
|
|
652
|
-
});
|
|
653
|
-
},
|
|
654
|
-
}, {
|
|
655
|
-
source: id,
|
|
656
|
-
})
|
|
657
|
-
: await context.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
|
|
658
|
-
.../** @type {Record<string, unknown>} */ (navigation.transitionTo),
|
|
659
|
-
onArrive,
|
|
660
|
-
}, {
|
|
661
|
-
source: id,
|
|
662
|
-
});
|
|
663
|
-
arrivalDeferred = hasActionResult(results);
|
|
664
|
-
}
|
|
665
|
-
if (Array.isArray(scene.preloadHints)) {
|
|
666
|
-
options.onPreloadHints?.(scene.preloadHints, scene, context);
|
|
667
|
-
}
|
|
668
|
-
options.onLayerState?.(scene, context);
|
|
669
|
-
if (!arrivalDeferred) {
|
|
670
|
-
onArrive();
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* @param {Record<string, unknown>} scene
|
|
676
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
677
|
-
* @param {unknown} event
|
|
678
|
-
* @param {() => void} onArrive
|
|
679
|
-
* @returns {Promise<boolean>} true when arrival will be reported asynchronously
|
|
680
|
-
*/
|
|
681
|
-
async function applyOrbitCameraScene(scene, context, event, onArrive) {
|
|
682
|
-
const camera = /** @type {Record<string, unknown>} */ (scene.camera);
|
|
683
|
-
const destinationOrbit = await resolveJourneyOrbit(camera, context);
|
|
684
|
-
if (!destinationOrbit) return false;
|
|
685
|
-
const lookTarget = await resolveJourneyTarget(camera.lookAt ?? camera.center, context)
|
|
686
|
-
?? destinationOrbit.center;
|
|
687
|
-
const source = { source: id };
|
|
688
|
-
await context.actions.invoke(SKYKIT_ACTIONS.navigation.cancel, null, source);
|
|
689
|
-
context.requestViewState({ targetPc: lookTarget }, id);
|
|
690
|
-
await context.actions.invoke(SKYKIT_ACTIONS.navigation.lockAt, {
|
|
691
|
-
...lookTarget,
|
|
692
|
-
...(destinationOrbit.normal ? { up: destinationOrbit.normal } : {}),
|
|
693
|
-
dwellSecs: resolveJourneyDwellSecs(camera, scene.travel),
|
|
694
|
-
recenterSpeed: 0.06,
|
|
695
|
-
}, source);
|
|
696
|
-
|
|
697
|
-
if (isInitialJourneyEvent(event)) {
|
|
698
|
-
const initialNormal = destinationOrbit.normal ?? { x: 0, y: 1, z: 0 };
|
|
699
|
-
const initialObserverPc = resolveInitialSceneObserverPc(scene)
|
|
700
|
-
?? defaultOrbitPosition(destinationOrbit.center, destinationOrbit.radius, initialNormal);
|
|
701
|
-
rememberResolvedJourneyOrbit(scene, {
|
|
702
|
-
...destinationOrbit,
|
|
703
|
-
normal: initialNormal,
|
|
704
|
-
});
|
|
705
|
-
context.requestViewState({
|
|
706
|
-
observerPc: initialObserverPc,
|
|
707
|
-
targetPc: lookTarget,
|
|
708
|
-
}, id);
|
|
709
|
-
await context.actions.invoke(SKYKIT_ACTIONS.navigation.orbit, {
|
|
710
|
-
center: destinationOrbit.center,
|
|
711
|
-
radius: destinationOrbit.radius,
|
|
712
|
-
angularSpeedRadPerSec: destinationOrbit.angularSpeedRadPerSec,
|
|
713
|
-
normal: initialNormal,
|
|
714
|
-
}, source);
|
|
715
|
-
return false;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
const travel = normalizeJourneySceneTravel(scene.travel);
|
|
719
|
-
const explicitRoutePoints = await resolveJourneyRoutePoints(scene, travel, context);
|
|
720
|
-
if (explicitRoutePoints.length >= 2) {
|
|
721
|
-
const arrivalAction = normalizeJourneyOrbitAction(
|
|
722
|
-
travel.arrivalAction ?? scene.travelPathArrivalAction,
|
|
723
|
-
destinationOrbit,
|
|
724
|
-
);
|
|
725
|
-
rememberResolvedJourneyOrbit(scene, arrivalAction);
|
|
726
|
-
const results = await context.actions.invoke(SKYKIT_ACTIONS.navigation.flyPolyline, {
|
|
727
|
-
points: explicitRoutePoints,
|
|
728
|
-
durationSecs: travel.durationSecs,
|
|
729
|
-
arrivalThreshold: travel.arrivalThreshold,
|
|
730
|
-
arrivalAction,
|
|
731
|
-
onArrive,
|
|
732
|
-
}, source);
|
|
733
|
-
return hasActionResult(results);
|
|
734
|
-
}
|
|
735
|
-
const route = createOrbitTransferRoute({
|
|
736
|
-
start: context.getViewState().observerPc,
|
|
737
|
-
sourceOrbit: await resolveSourceJourneyOrbit(event, context),
|
|
738
|
-
destinationOrbit,
|
|
739
|
-
durationSecs: travel.durationSecs,
|
|
740
|
-
sampleStepSecs: travel.sampleStepSecs,
|
|
741
|
-
});
|
|
742
|
-
if (route && Array.isArray(route.points) && route.points.length >= 2) {
|
|
743
|
-
rememberResolvedJourneyOrbit(scene, route.arrivalAction);
|
|
744
|
-
const results = await context.actions.invoke(SKYKIT_ACTIONS.navigation.flyPolyline, {
|
|
745
|
-
points: route.points,
|
|
746
|
-
durationSecs: travel.durationSecs,
|
|
747
|
-
currentSpeed: route.departureSpeed,
|
|
748
|
-
arrivalSpeed: route.arrivalSpeed,
|
|
749
|
-
arrivalThreshold: travel.arrivalThreshold,
|
|
750
|
-
arrivalAction: route.arrivalAction,
|
|
751
|
-
onArrive,
|
|
752
|
-
}, source);
|
|
753
|
-
return hasActionResult(results);
|
|
754
|
-
}
|
|
755
|
-
const fallbackNormal = destinationOrbit.normal ?? { x: 0, y: 1, z: 0 };
|
|
756
|
-
rememberResolvedJourneyOrbit(scene, {
|
|
757
|
-
...destinationOrbit,
|
|
758
|
-
normal: fallbackNormal,
|
|
759
|
-
});
|
|
760
|
-
await context.actions.invoke(SKYKIT_ACTIONS.navigation.orbit, {
|
|
761
|
-
center: destinationOrbit.center,
|
|
762
|
-
radius: destinationOrbit.radius,
|
|
763
|
-
angularSpeedRadPerSec: destinationOrbit.angularSpeedRadPerSec,
|
|
764
|
-
normal: fallbackNormal,
|
|
765
|
-
}, source);
|
|
766
|
-
return false;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/**
|
|
770
|
-
* @param {unknown} transitionTo
|
|
771
|
-
* @returns {Record<string, unknown>}
|
|
772
|
-
*/
|
|
773
|
-
function createRouteArrivalTransitionPayload(transitionTo) {
|
|
774
|
-
const payload = /** @type {Record<string, unknown>} */ (
|
|
775
|
-
transitionTo && typeof transitionTo === 'object' ? transitionTo : {}
|
|
776
|
-
);
|
|
777
|
-
return {
|
|
778
|
-
...payload,
|
|
779
|
-
movement: {
|
|
780
|
-
...(payload.movement && typeof payload.movement === 'object'
|
|
781
|
-
? /** @type {Record<string, unknown>} */ (payload.movement)
|
|
782
|
-
: {}),
|
|
783
|
-
durationSecs: 0.001,
|
|
784
|
-
},
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* @param {Record<string, unknown>} scene
|
|
790
|
-
* @param {Record<string, unknown>} travel
|
|
791
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
792
|
-
* @returns {Promise<Vector3Like[]>}
|
|
793
|
-
*/
|
|
794
|
-
async function resolveJourneyRoutePoints(scene, travel, context) {
|
|
795
|
-
const rawPoints = resolveJourneyRoutePointSource(scene, travel);
|
|
796
|
-
if (!rawPoints) return [];
|
|
797
|
-
const points = [];
|
|
798
|
-
for (const point of Array.from(rawPoints)) {
|
|
799
|
-
const resolved = await resolveJourneyTarget(point, context);
|
|
800
|
-
if (resolved) points.push(resolved);
|
|
801
|
-
}
|
|
802
|
-
return points;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
/**
|
|
806
|
-
* @param {Record<string, unknown>} scene
|
|
807
|
-
* @param {Record<string, unknown>} travel
|
|
808
|
-
* @returns {Iterable<unknown> | null}
|
|
809
|
-
*/
|
|
810
|
-
function resolveJourneyRoutePointSource(scene, travel) {
|
|
811
|
-
return firstIterable(
|
|
812
|
-
scene.travelPathPc,
|
|
813
|
-
scene.travelPath,
|
|
814
|
-
travel.pathPointsPc,
|
|
815
|
-
travel.pointsPc,
|
|
816
|
-
travel.points,
|
|
817
|
-
);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* @param {...unknown} candidates
|
|
822
|
-
* @returns {Iterable<unknown> | null}
|
|
823
|
-
*/
|
|
824
|
-
function firstIterable(...candidates) {
|
|
825
|
-
for (const candidate of candidates) {
|
|
826
|
-
if (
|
|
827
|
-
candidate
|
|
828
|
-
&& typeof /** @type {{ [Symbol.iterator]?: unknown }} */ (candidate)[Symbol.iterator] === 'function'
|
|
829
|
-
) {
|
|
830
|
-
return /** @type {Iterable<unknown>} */ (candidate);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
return null;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
/**
|
|
837
|
-
* @param {unknown} value
|
|
838
|
-
* @param {{ center: Vector3Like; radius: number; angularSpeedRadPerSec: number; normal?: Vector3Like }} fallback
|
|
839
|
-
* @returns {{ type: 'orbit'; center: Vector3Like; radius: number; angularSpeedRadPerSec: number; normal: Vector3Like }}
|
|
840
|
-
*/
|
|
841
|
-
function normalizeJourneyOrbitAction(value, fallback) {
|
|
842
|
-
const source = value && typeof value === 'object'
|
|
843
|
-
? /** @type {Record<string, unknown>} */ (value)
|
|
844
|
-
: {};
|
|
845
|
-
const fallbackNormal = fallback.normal ?? { x: 0, y: 1, z: 0 };
|
|
846
|
-
return {
|
|
847
|
-
type: 'orbit',
|
|
848
|
-
center: normalizeOptionalVector3(source.center) ?? cloneVector3(fallback.center),
|
|
849
|
-
radius: positiveFinite(source.radius ?? source.radiusPc, fallback.radius),
|
|
850
|
-
angularSpeedRadPerSec: finiteNumber(
|
|
851
|
-
source.angularSpeedRadPerSec ?? source.angularSpeed,
|
|
852
|
-
fallback.angularSpeedRadPerSec,
|
|
853
|
-
),
|
|
854
|
-
normal: normalizeDirectionVector(source.normal, fallbackNormal),
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
/**
|
|
859
|
-
* @param {Record<string, unknown>} camera
|
|
860
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
861
|
-
*/
|
|
862
|
-
async function resolveJourneyOrbit(camera, context) {
|
|
863
|
-
const center = await resolveJourneyTarget(camera.center, context);
|
|
864
|
-
if (!center) return null;
|
|
865
|
-
return {
|
|
866
|
-
center,
|
|
867
|
-
radius: positiveFinite(camera.radiusPc, 1),
|
|
868
|
-
angularSpeedRadPerSec: finiteNumber(camera.angularSpeedRadPerSec, 0.1),
|
|
869
|
-
...(camera.normal != null ? { normal: normalizeDirectionVector(camera.normal, { x: 0, y: 1, z: 0 }) } : {}),
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* @param {Record<string, unknown>} scene
|
|
875
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
876
|
-
* @param {unknown} event
|
|
877
|
-
*/
|
|
878
|
-
async function notifySceneArrive(scene, context, event) {
|
|
879
|
-
try {
|
|
880
|
-
await options.onSceneArrive?.(scene, context, event);
|
|
881
|
-
} catch (error) {
|
|
882
|
-
context.emit?.({
|
|
883
|
-
type: 'journey/scene-arrive/error',
|
|
884
|
-
pluginId: id,
|
|
885
|
-
message: 'Failed to handle journey scene arrival.',
|
|
886
|
-
error,
|
|
887
|
-
event,
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* @param {unknown} input
|
|
894
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
895
|
-
*/
|
|
896
|
-
async function resolveJourneyTarget(input, context) {
|
|
897
|
-
if (typeof input === 'string') {
|
|
898
|
-
const targets = resolveJourneyTargets();
|
|
899
|
-
const target = targets[input];
|
|
900
|
-
if (target && typeof target === 'object' && target.positionPc) {
|
|
901
|
-
return normalizeVector3(target.positionPc, { x: 0, y: 0, z: 0 });
|
|
902
|
-
}
|
|
903
|
-
return null;
|
|
904
|
-
}
|
|
905
|
-
return await resolveSpatialTarget(
|
|
906
|
-
/** @type {import('@found-in-space/spatial').SpatialTargetInput} */ (input),
|
|
907
|
-
{ observerPc: context.getViewState().observerPc },
|
|
908
|
-
);
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
function resolveJourneyTargets() {
|
|
912
|
-
const graph = controller?.graph;
|
|
913
|
-
if (graph && typeof graph === 'object' && 'targets' in graph) {
|
|
914
|
-
return /** @type {Record<string, { positionPc?: unknown }>} */ (
|
|
915
|
-
/** @type {Record<string, unknown>} */ (graph).targets ?? {}
|
|
916
|
-
);
|
|
917
|
-
}
|
|
918
|
-
return /** @type {Record<string, { positionPc?: unknown }>} */ (options.journey?.targets ?? {});
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
/**
|
|
922
|
-
* @param {unknown} event
|
|
923
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
924
|
-
*/
|
|
925
|
-
async function resolveSourceJourneyOrbit(event, context) {
|
|
926
|
-
const previousSceneId = event && typeof event === 'object'
|
|
927
|
-
? /** @type {{ previousSceneId?: unknown }} */ (event).previousSceneId
|
|
928
|
-
: null;
|
|
929
|
-
if (typeof previousSceneId !== 'string') return null;
|
|
930
|
-
const resolved = resolvedJourneyOrbits.get(previousSceneId);
|
|
931
|
-
if (resolved) return resolved;
|
|
932
|
-
const previousScene = controller?.graph.getScene(previousSceneId);
|
|
933
|
-
const camera = previousScene?.camera && typeof previousScene.camera === 'object'
|
|
934
|
-
? /** @type {Record<string, unknown>} */ (previousScene.camera)
|
|
935
|
-
: null;
|
|
936
|
-
return camera && camera.type === 'orbit' ? resolveJourneyOrbit(camera, context) : null;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
/** @param {Record<string, unknown>} scene */
|
|
940
|
-
function resolveInitialSceneObserverPc(scene) {
|
|
941
|
-
const view = scene.view && typeof scene.view === 'object'
|
|
942
|
-
? /** @type {{ observerPc?: unknown }} */ (scene.view)
|
|
943
|
-
: null;
|
|
944
|
-
return normalizeOptionalVector3(view?.observerPc);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
/**
|
|
948
|
-
* @param {Record<string, unknown>} scene
|
|
949
|
-
* @param {{ center: Vector3Like; radius: number; angularSpeedRadPerSec: number; normal: Vector3Like }} orbit
|
|
950
|
-
*/
|
|
951
|
-
function rememberResolvedJourneyOrbit(scene, orbit) {
|
|
952
|
-
const sceneId = typeof scene.sceneId === 'string' ? scene.sceneId : null;
|
|
953
|
-
if (!sceneId) return;
|
|
954
|
-
resolvedJourneyOrbits.set(sceneId, {
|
|
955
|
-
center: cloneVector3(orbit.center),
|
|
956
|
-
radius: orbit.radius,
|
|
957
|
-
angularSpeedRadPerSec: orbit.angularSpeedRadPerSec,
|
|
958
|
-
normal: cloneVector3(orbit.normal),
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
/**
|
|
963
|
-
* @param {import('@found-in-space/journey').TimedJourneyFrame} frameState
|
|
964
|
-
* @param {{ viewer: import('./index.d.ts').SkykitViewer; view: import('./index.d.ts').SkykitViewState }} frame
|
|
965
|
-
*/
|
|
966
|
-
function applyTimedFrame(frameState, frame) {
|
|
967
|
-
const context = pluginContext;
|
|
968
|
-
const hookResult = options.applyFrame?.(frameState, context, frame);
|
|
969
|
-
if (hookResult === false) return;
|
|
970
|
-
frame.viewer.requestViewState({
|
|
971
|
-
observerPc: frameState.observerPc,
|
|
972
|
-
orientationIcrs: frameState.orientationIcrs,
|
|
973
|
-
targetPc: frameState.targetPc,
|
|
974
|
-
motion: {
|
|
975
|
-
velocityPcPerSec: frameState.velocityPcPerSec,
|
|
976
|
-
speedPcPerSec: frameState.speedPcPerSec,
|
|
977
|
-
},
|
|
978
|
-
}, id);
|
|
979
|
-
if (frameState.cue && frameState.cue.id !== lastCueId) {
|
|
980
|
-
lastCueId = frameState.cue.id;
|
|
981
|
-
options.onCue?.(frameState.cue, frameState, context);
|
|
982
|
-
} else if (!frameState.cue) {
|
|
983
|
-
lastCueId = null;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
function getSnapshot() {
|
|
988
|
-
return {
|
|
989
|
-
id,
|
|
990
|
-
disposed,
|
|
991
|
-
playing,
|
|
992
|
-
currentTimeSecs,
|
|
993
|
-
initialSceneApplied,
|
|
994
|
-
timedPreloadHintsEmitted,
|
|
995
|
-
controller: controller?.getSnapshot?.() ?? null,
|
|
996
|
-
timedJourney: evaluator
|
|
997
|
-
? {
|
|
998
|
-
durationSecs: evaluator.durationSecs,
|
|
999
|
-
journeyId: evaluator.journey.id,
|
|
1000
|
-
}
|
|
1001
|
-
: null,
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
405
|
|
|
1006
406
|
/**
|
|
1007
407
|
* Strategy-only convenience helper. View-bound lookahead hints stay in preload
|
|
@@ -1594,108 +994,6 @@ function getDefaultEventTarget() {
|
|
|
1594
994
|
return typeof globalThis.addEventListener === 'function' ? globalThis : null;
|
|
1595
995
|
}
|
|
1596
996
|
|
|
1597
|
-
/** @param {Record<string, unknown>} scene */
|
|
1598
|
-
function isOrbitCameraScene(scene) {
|
|
1599
|
-
return Boolean(
|
|
1600
|
-
scene.camera
|
|
1601
|
-
&& typeof scene.camera === 'object'
|
|
1602
|
-
&& /** @type {{ type?: unknown }} */ (scene.camera).type === 'orbit',
|
|
1603
|
-
);
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
/** @param {unknown} event */
|
|
1607
|
-
function isInitialJourneyEvent(event) {
|
|
1608
|
-
return Boolean(
|
|
1609
|
-
event
|
|
1610
|
-
&& typeof event === 'object'
|
|
1611
|
-
&& /** @type {{ type?: unknown; previousSceneId?: unknown }} */ (event).type === 'journey/initial',
|
|
1612
|
-
);
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
/**
|
|
1616
|
-
* @param {unknown} value
|
|
1617
|
-
* @returns {Record<string, unknown> & { durationSecs: number; sampleStepSecs: number; arrivalThreshold: number }}
|
|
1618
|
-
*/
|
|
1619
|
-
function normalizeJourneySceneTravel(value) {
|
|
1620
|
-
const source = /** @type {Record<string, unknown>} */ (
|
|
1621
|
-
value && typeof value === 'object' ? value : {}
|
|
1622
|
-
);
|
|
1623
|
-
return {
|
|
1624
|
-
...source,
|
|
1625
|
-
durationSecs: positiveFinite(source.durationSecs, 5),
|
|
1626
|
-
sampleStepSecs: positiveFinite(source.sampleStepSecs, 1 / 60),
|
|
1627
|
-
arrivalThreshold: positiveFinite(source.arrivalThreshold, 0.05),
|
|
1628
|
-
};
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
/**
|
|
1632
|
-
* @param {Record<string, unknown>} camera
|
|
1633
|
-
* @param {unknown} travel
|
|
1634
|
-
*/
|
|
1635
|
-
function resolveJourneyDwellSecs(camera, travel) {
|
|
1636
|
-
if (camera.dwellSecs != null) return Math.max(0, finiteNumber(camera.dwellSecs, 0));
|
|
1637
|
-
if (travel && typeof travel === 'object' && 'dwellSecs' in travel) {
|
|
1638
|
-
return Math.max(0, finiteNumber(/** @type {{ dwellSecs?: unknown }} */ (travel).dwellSecs, 0));
|
|
1639
|
-
}
|
|
1640
|
-
return 0;
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
/**
|
|
1644
|
-
* @param {Vector3Like} center
|
|
1645
|
-
* @param {number} radius
|
|
1646
|
-
* @param {Vector3Like} normal
|
|
1647
|
-
* @returns {Vector3Like}
|
|
1648
|
-
*/
|
|
1649
|
-
function defaultOrbitPosition(center, radius, normal) {
|
|
1650
|
-
const axis = Math.abs(normal.x) < 0.9 ? { x: 1, y: 0, z: 0 } : { x: 0, y: 1, z: 0 };
|
|
1651
|
-
const projected = projectOnPlane(axis, normal);
|
|
1652
|
-
const length = Math.hypot(projected.x, projected.y, projected.z);
|
|
1653
|
-
const direction = length > 1e-9
|
|
1654
|
-
? { x: projected.x / length, y: projected.y / length, z: projected.z / length }
|
|
1655
|
-
: { x: 1, y: 0, z: 0 };
|
|
1656
|
-
return {
|
|
1657
|
-
x: center.x + direction.x * radius,
|
|
1658
|
-
y: center.y + direction.y * radius,
|
|
1659
|
-
z: center.z + direction.z * radius,
|
|
1660
|
-
};
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
/**
|
|
1664
|
-
* @param {unknown} value
|
|
1665
|
-
* @param {Vector3Like} fallback
|
|
1666
|
-
* @returns {Vector3Like}
|
|
1667
|
-
*/
|
|
1668
|
-
function normalizeDirectionVector(value, fallback) {
|
|
1669
|
-
const vector = normalizeVector3(value, fallback);
|
|
1670
|
-
const length = Math.hypot(vector.x, vector.y, vector.z);
|
|
1671
|
-
return length > 1e-9
|
|
1672
|
-
? { x: vector.x / length, y: vector.y / length, z: vector.z / length }
|
|
1673
|
-
: cloneVector3(fallback);
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
/** @param {unknown} value */
|
|
1677
|
-
function normalizeOptionalVector3(value) {
|
|
1678
|
-
if (!value || typeof value !== 'object') return null;
|
|
1679
|
-
const vector = /** @type {{ x?: unknown; y?: unknown; z?: unknown }} */ (value);
|
|
1680
|
-
const x = Number(vector.x);
|
|
1681
|
-
const y = Number(vector.y);
|
|
1682
|
-
const z = Number(vector.z);
|
|
1683
|
-
return [x, y, z].every(Number.isFinite) ? { x, y, z } : null;
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
/**
|
|
1687
|
-
* @param {Vector3Like} vector
|
|
1688
|
-
* @param {Vector3Like} normal
|
|
1689
|
-
* @returns {Vector3Like}
|
|
1690
|
-
*/
|
|
1691
|
-
function projectOnPlane(vector, normal) {
|
|
1692
|
-
const amount = vector.x * normal.x + vector.y * normal.y + vector.z * normal.z;
|
|
1693
|
-
return {
|
|
1694
|
-
x: vector.x - normal.x * amount,
|
|
1695
|
-
y: vector.y - normal.y * amount,
|
|
1696
|
-
z: vector.z - normal.z * amount,
|
|
1697
|
-
};
|
|
1698
|
-
}
|
|
1699
997
|
|
|
1700
998
|
/**
|
|
1701
999
|
* @param {unknown} payload
|
|
@@ -1734,11 +1032,6 @@ function resolveOnArrive(payload) {
|
|
|
1734
1032
|
: null;
|
|
1735
1033
|
}
|
|
1736
1034
|
|
|
1737
|
-
/** @param {PromiseSettledResult<unknown>[]} results */
|
|
1738
|
-
function hasActionResult(results) {
|
|
1739
|
-
return results.some((result) => result.status === 'fulfilled' && result.value != null);
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
1035
|
/** @param {Record<string, unknown>} targetSource */
|
|
1743
1036
|
function resolveTransitionPositionInput(targetSource) {
|
|
1744
1037
|
if (targetSource.observerPc !== undefined) return targetSource.observerPc;
|
|
@@ -1761,29 +1054,6 @@ function isSpatialTargetLike(value) {
|
|
|
1761
1054
|
return Array.isArray(value) && value.length >= 3;
|
|
1762
1055
|
}
|
|
1763
1056
|
|
|
1764
|
-
/** @param {unknown} payload */
|
|
1765
|
-
function resolveSceneId(payload) {
|
|
1766
|
-
if (typeof payload === 'string') return payload;
|
|
1767
|
-
if (!payload || typeof payload !== 'object') return null;
|
|
1768
|
-
const source = /** @type {Record<string, unknown>} */ (payload);
|
|
1769
|
-
const value = source.chapterId ?? source.sceneId ?? source.id;
|
|
1770
|
-
return typeof value === 'string' ? value : null;
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
/** @param {unknown} payload */
|
|
1774
|
-
function resolveTimeSecs(payload) {
|
|
1775
|
-
if (Number.isFinite(Number(payload))) return Number(payload);
|
|
1776
|
-
if (!payload || typeof payload !== 'object') return 0;
|
|
1777
|
-
const value = /** @type {Record<string, unknown>} */ (payload).timeSecs
|
|
1778
|
-
?? /** @type {Record<string, unknown>} */ (payload).sceneTimeSecs
|
|
1779
|
-
?? /** @type {Record<string, unknown>} */ (payload).time;
|
|
1780
|
-
return finiteNumber(value, 0);
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
/** @param {number} timeSecs @param {number} durationSecs */
|
|
1784
|
-
function clampTime(timeSecs, durationSecs) {
|
|
1785
|
-
return Math.min(Math.max(0, finiteNumber(timeSecs, 0)), Math.max(0, finiteNumber(durationSecs, 0)));
|
|
1786
|
-
}
|
|
1787
1057
|
|
|
1788
1058
|
/**
|
|
1789
1059
|
* @param {unknown} value
|