@found-in-space/skykit 0.2.0-alpha.1 → 0.2.0-alpha.20260528
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 +142 -11
- package/examples/custom-object-layer/custom-object-layer.js +1 -24
- package/examples/xr-free-roam/index.html +62 -4
- package/examples/xr-free-roam/xr-free-roam.css +249 -18
- package/examples/xr-free-roam/xr-free-roam.js +675 -274
- package/package.json +22 -6
- package/src/__tests__/skykit-anchored-images.test.js +32 -4
- package/src/__tests__/skykit-browser.test.js +267 -0
- package/src/__tests__/skykit-data.test.js +131 -0
- package/src/__tests__/skykit-parallax.test.js +4 -4
- package/src/__tests__/skykit-touch-os.test.js +71 -0
- package/src/__tests__/skykit-xr.test.js +179 -2
- package/src/__tests__/skykit.test.js +142 -506
- package/src/actions.js +0 -8
- package/src/anchored-images.js +14 -15
- package/src/browser-addons.d.ts +16 -0
- package/src/browser-addons.js +155 -0
- package/src/browser-constellations.d.ts +13 -0
- package/src/browser-constellations.js +387 -0
- package/src/browser.d.ts +81 -0
- package/src/browser.js +192 -13
- package/src/data.d.ts +133 -0
- package/src/data.js +447 -0
- package/src/embed.d.ts +5 -0
- package/src/embed.js +53 -2
- package/src/hr-diagram.js +23 -5
- package/src/index.d.ts +21 -73
- package/src/index.js +0 -1
- package/src/plugins.js +22 -708
- package/src/three-shim.d.ts +32 -0
- package/src/touch-os.d.ts +70 -0
- package/src/touch-os.js +275 -0
- package/src/utils.js +96 -6
- package/src/viewer-entry.d.ts +10 -0
- package/src/viewer-entry.js +4 -0
- package/src/viewer.js +110 -12
- package/src/xr/plugins.js +298 -13
- package/src/xr/session.js +60 -14
- package/src/xr.d.ts +40 -0
- package/src/xr.js +2 -0
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,9 +11,9 @@ import {
|
|
|
16
11
|
|
|
17
12
|
import {
|
|
18
13
|
IDENTITY_QUATERNION as SPATIAL_IDENTITY_QUATERNION,
|
|
19
|
-
createOrbitTransferRoute,
|
|
20
14
|
createSpatialPoseTransition,
|
|
21
15
|
createSpatialNavigationAutomation,
|
|
16
|
+
resolveSpatialLookAt,
|
|
22
17
|
resolveSpatialTarget,
|
|
23
18
|
} from '@found-in-space/spatial';
|
|
24
19
|
|
|
@@ -219,8 +214,12 @@ export function createSkykitNavigationPlugin(options = {}) {
|
|
|
219
214
|
id,
|
|
220
215
|
setup(context) {
|
|
221
216
|
const threeContext = /** @type {import('./index.d.ts').SkykitThreePluginContext} */ (context);
|
|
222
|
-
threeContext.addPart(part);
|
|
223
|
-
|
|
217
|
+
const removePart = threeContext.addPart(part);
|
|
218
|
+
const unregisterActions = registerNavigationActions(threeContext);
|
|
219
|
+
return () => {
|
|
220
|
+
unregisterActions();
|
|
221
|
+
removePart();
|
|
222
|
+
};
|
|
224
223
|
},
|
|
225
224
|
getSnapshot: () => part.getSnapshot?.() ?? null,
|
|
226
225
|
};
|
|
@@ -366,10 +365,24 @@ export function createSkykitNavigationPlugin(options = {}) {
|
|
|
366
365
|
const position = positionInput === undefined
|
|
367
366
|
? current.observerPc
|
|
368
367
|
: await resolveTarget(positionInput, context) ?? current.observerPc;
|
|
368
|
+
const lookAtInput = targetSource.lookAt;
|
|
369
|
+
const resolvedLook = lookAtInput !== undefined
|
|
370
|
+
? await resolveSpatialLookAt(lookAtInput, {
|
|
371
|
+
observerPc: position,
|
|
372
|
+
resolveBookmark: typeof options.resolveBookmark === 'function'
|
|
373
|
+
? (bookmarkId, original) => options.resolveBookmark?.(
|
|
374
|
+
bookmarkId,
|
|
375
|
+
/** @type {import('@found-in-space/spatial').SpatialTargetInput} */ (original),
|
|
376
|
+
context,
|
|
377
|
+
) ?? null
|
|
378
|
+
: undefined,
|
|
379
|
+
})
|
|
380
|
+
: null;
|
|
369
381
|
const orientationInput = targetSource.orientationIcrs
|
|
370
382
|
?? (targetSource.orientation && isQuaternionLike(targetSource.orientation) ? targetSource.orientation : undefined)
|
|
371
383
|
?? source.orientationIcrs;
|
|
372
|
-
const orientation =
|
|
384
|
+
const orientation = resolvedLook?.orientationIcrs
|
|
385
|
+
?? normalizeQuaternion(orientationInput, current.orientationIcrs ?? IDENTITY_QUATERNION);
|
|
373
386
|
return createSpatialPoseTransition({
|
|
374
387
|
from: {
|
|
375
388
|
position: current.observerPc,
|
|
@@ -389,575 +402,6 @@ export function createSkykitNavigationPlugin(options = {}) {
|
|
|
389
402
|
}
|
|
390
403
|
}
|
|
391
404
|
|
|
392
|
-
/**
|
|
393
|
-
* @param {import('./index.d.ts').SkykitJourneyPluginOptions} [options]
|
|
394
|
-
* @returns {SkykitPlugin & { getSnapshot(): unknown }}
|
|
395
|
-
*/
|
|
396
|
-
export function createSkykitJourneyPlugin(options = {}) {
|
|
397
|
-
const id = options.id ?? 'journey';
|
|
398
|
-
const controller = options.controller
|
|
399
|
-
?? (options.journey
|
|
400
|
-
? createJourneyController({ graph: options.journey })
|
|
401
|
-
: (options.graph || options.scenes ? createJourneyController(options) : null));
|
|
402
|
-
const evaluator = options.evaluator
|
|
403
|
-
?? (options.timedJourney ? createTimedJourneyEvaluator(options.timedJourney, options.evaluatorOptions) : null);
|
|
404
|
-
let disposed = false;
|
|
405
|
-
let playing = options.autoPlay === true;
|
|
406
|
-
let currentTimeSecs = Math.max(0, finiteNumber(options.startTimeSecs, 0));
|
|
407
|
-
let lastCueId = /** @type {string | null} */ (null);
|
|
408
|
-
let initialSceneApplied = false;
|
|
409
|
-
let timedPreloadHintsEmitted = false;
|
|
410
|
-
/** @type {import('./index.d.ts').SkykitThreePluginContext | null} */
|
|
411
|
-
let pluginContext = null;
|
|
412
|
-
const resolvedJourneyOrbits = new Map();
|
|
413
|
-
/** @type {(() => void) | null} */
|
|
414
|
-
let unsubscribeController = null;
|
|
415
|
-
/** @type {Promise<unknown>} */
|
|
416
|
-
let pendingSceneApplication = Promise.resolve(null);
|
|
417
|
-
|
|
418
|
-
/** @type {SkykitThreePart} */
|
|
419
|
-
const part = {
|
|
420
|
-
id,
|
|
421
|
-
priority: options.priority,
|
|
422
|
-
attach(context) {
|
|
423
|
-
pluginContext = context;
|
|
424
|
-
if (controller) {
|
|
425
|
-
unsubscribeController = controller.subscribe((event) => {
|
|
426
|
-
queueSceneApplication(event.spec, context, event);
|
|
427
|
-
});
|
|
428
|
-
applyInitialScene(context);
|
|
429
|
-
}
|
|
430
|
-
emitTimedPreloadHints(context, 'attach');
|
|
431
|
-
},
|
|
432
|
-
update(frame) {
|
|
433
|
-
if (disposed || !evaluator || !playing) return;
|
|
434
|
-
currentTimeSecs = Math.min(
|
|
435
|
-
evaluator.durationSecs,
|
|
436
|
-
currentTimeSecs + Math.max(0, finiteNumber(frame.deltaSeconds, 0)),
|
|
437
|
-
);
|
|
438
|
-
applyTimedFrame(evaluator.evaluate(currentTimeSecs), frame);
|
|
439
|
-
if (currentTimeSecs >= evaluator.durationSecs) {
|
|
440
|
-
playing = options.loop === true;
|
|
441
|
-
currentTimeSecs = playing ? 0 : evaluator.durationSecs;
|
|
442
|
-
}
|
|
443
|
-
},
|
|
444
|
-
detach() {
|
|
445
|
-
unsubscribeController?.();
|
|
446
|
-
unsubscribeController = null;
|
|
447
|
-
pluginContext = null;
|
|
448
|
-
},
|
|
449
|
-
dispose() {
|
|
450
|
-
disposed = true;
|
|
451
|
-
this.detach?.();
|
|
452
|
-
if (options.disposeController !== false) controller?.dispose?.();
|
|
453
|
-
},
|
|
454
|
-
getSnapshot,
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
return {
|
|
458
|
-
id,
|
|
459
|
-
setup(context) {
|
|
460
|
-
const threeContext = /** @type {import('./index.d.ts').SkykitThreePluginContext} */ (context);
|
|
461
|
-
threeContext.addPart(part);
|
|
462
|
-
threeContext.addDisposable(registerJourneyActions(threeContext));
|
|
463
|
-
},
|
|
464
|
-
getSnapshot,
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
/** @param {import('./index.d.ts').SkykitThreePluginContext} context */
|
|
468
|
-
function registerJourneyActions(context) {
|
|
469
|
-
const unregisters = [
|
|
470
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.goToChapter, async ({ payload }) => {
|
|
471
|
-
await pendingSceneApplication;
|
|
472
|
-
const sceneId = resolveSceneId(payload);
|
|
473
|
-
const spec = sceneId && controller ? controller.goTo(sceneId, { source: SKYKIT_ACTIONS.journey.goToChapter }) : null;
|
|
474
|
-
await pendingSceneApplication;
|
|
475
|
-
return spec;
|
|
476
|
-
}, { label: 'Go to journey chapter' }),
|
|
477
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.next, async () => {
|
|
478
|
-
await pendingSceneApplication;
|
|
479
|
-
const spec = controller?.next({ source: SKYKIT_ACTIONS.journey.next }) ?? null;
|
|
480
|
-
await pendingSceneApplication;
|
|
481
|
-
return spec;
|
|
482
|
-
}, {
|
|
483
|
-
label: 'Next journey chapter',
|
|
484
|
-
}),
|
|
485
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.previous, async () => {
|
|
486
|
-
await pendingSceneApplication;
|
|
487
|
-
const spec = controller?.previous({ source: SKYKIT_ACTIONS.journey.previous }) ?? null;
|
|
488
|
-
await pendingSceneApplication;
|
|
489
|
-
return spec;
|
|
490
|
-
}, {
|
|
491
|
-
label: 'Previous journey chapter',
|
|
492
|
-
}),
|
|
493
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.seek, ({ payload }) => {
|
|
494
|
-
currentTimeSecs = clampTime(resolveTimeSecs(payload), evaluator?.durationSecs ?? Number.POSITIVE_INFINITY);
|
|
495
|
-
if (evaluator) applyTimedFrame(evaluator.evaluate(currentTimeSecs), { viewer: context.viewer, view: context.getViewState() });
|
|
496
|
-
emitTimedPreloadHints(context, 'seek');
|
|
497
|
-
return currentTimeSecs;
|
|
498
|
-
}, { label: 'Seek journey time' }),
|
|
499
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.play, ({ payload }) => {
|
|
500
|
-
if (payload && typeof payload === 'object' && 'timeSecs' in payload) {
|
|
501
|
-
currentTimeSecs = clampTime(resolveTimeSecs(payload), evaluator?.durationSecs ?? Number.POSITIVE_INFINITY);
|
|
502
|
-
}
|
|
503
|
-
playing = true;
|
|
504
|
-
emitTimedPreloadHints(context, 'play');
|
|
505
|
-
return currentTimeSecs;
|
|
506
|
-
}, { label: 'Play journey' }),
|
|
507
|
-
context.actions.registerAction(SKYKIT_ACTIONS.journey.pause, () => {
|
|
508
|
-
playing = false;
|
|
509
|
-
return currentTimeSecs;
|
|
510
|
-
}, { label: 'Pause journey' }),
|
|
511
|
-
];
|
|
512
|
-
return () => {
|
|
513
|
-
for (const unregister of unregisters.reverse()) unregister();
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/** @param {import('./index.d.ts').SkykitThreePluginContext} context */
|
|
518
|
-
function applyInitialScene(context) {
|
|
519
|
-
if (!controller || initialSceneApplied) return;
|
|
520
|
-
const snapshot = controller.getSnapshot();
|
|
521
|
-
const sceneId = snapshot.activeSceneId;
|
|
522
|
-
if (!sceneId) {
|
|
523
|
-
initialSceneApplied = true;
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
initialSceneApplied = true;
|
|
527
|
-
const spec = controller.graph.resolveSceneSpec(sceneId, { fromSceneId: snapshot.previousSceneId });
|
|
528
|
-
queueSceneApplication(spec, context, {
|
|
529
|
-
type: 'journey/initial',
|
|
530
|
-
sceneId,
|
|
531
|
-
previousSceneId: snapshot.previousSceneId,
|
|
532
|
-
source: 'attach',
|
|
533
|
-
spec,
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* @param {unknown} spec
|
|
539
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
540
|
-
* @param {unknown} event
|
|
541
|
-
*/
|
|
542
|
-
function queueSceneApplication(spec, context, event) {
|
|
543
|
-
pendingSceneApplication = Promise.resolve(applySceneSpec(spec, context, event)).catch((error) => {
|
|
544
|
-
context.emit?.({
|
|
545
|
-
type: 'journey/scene/error',
|
|
546
|
-
pluginId: id,
|
|
547
|
-
message: 'Failed to apply journey scene.',
|
|
548
|
-
error,
|
|
549
|
-
event,
|
|
550
|
-
});
|
|
551
|
-
return null;
|
|
552
|
-
});
|
|
553
|
-
return pendingSceneApplication;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
558
|
-
* @param {string} source
|
|
559
|
-
*/
|
|
560
|
-
function emitTimedPreloadHints(context, source) {
|
|
561
|
-
if (!evaluator || timedPreloadHintsEmitted) return;
|
|
562
|
-
const preloadHints = typeof evaluator.getPreloadHints === 'function'
|
|
563
|
-
? evaluator.getPreloadHints()
|
|
564
|
-
: evaluator.evaluate(currentTimeSecs).preloadHints;
|
|
565
|
-
timedPreloadHintsEmitted = true;
|
|
566
|
-
if (preloadHints.length > 0) {
|
|
567
|
-
options.onPreloadHints?.(preloadHints, {
|
|
568
|
-
type: 'journey/timed-preload',
|
|
569
|
-
journeyId: evaluator.journey.id,
|
|
570
|
-
source,
|
|
571
|
-
}, context);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
/**
|
|
576
|
-
* @param {unknown} spec
|
|
577
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
578
|
-
* @param {unknown} event
|
|
579
|
-
*/
|
|
580
|
-
async function applySceneSpec(spec, context, event) {
|
|
581
|
-
const scene = /** @type {Record<string, unknown> | null} */ (spec && typeof spec === 'object' ? spec : null);
|
|
582
|
-
options.onScene?.(scene, context, event);
|
|
583
|
-
if (!scene) return;
|
|
584
|
-
const onArrive = () => {
|
|
585
|
-
void notifySceneArrive(scene, context, event);
|
|
586
|
-
};
|
|
587
|
-
let arrivalDeferred = false;
|
|
588
|
-
if (scene.view && typeof scene.view === 'object') {
|
|
589
|
-
context.requestViewState(/** @type {Partial<import('./index.d.ts').SkykitViewState>} */ (scene.view), id);
|
|
590
|
-
}
|
|
591
|
-
const navigation = /** @type {Record<string, unknown> | null} */ (scene.navigation && typeof scene.navigation === 'object' ? scene.navigation : null);
|
|
592
|
-
if (isOrbitCameraScene(scene)) {
|
|
593
|
-
arrivalDeferred = await applyOrbitCameraScene(scene, context, event, onArrive);
|
|
594
|
-
} else if (navigation?.transitionTo) {
|
|
595
|
-
const travel = normalizeJourneySceneTravel(scene.travel);
|
|
596
|
-
const explicitRoutePoints = await resolveJourneyRoutePoints(scene, travel, context);
|
|
597
|
-
const results = explicitRoutePoints.length >= 2
|
|
598
|
-
? await context.actions.invoke(SKYKIT_ACTIONS.navigation.flyPolyline, {
|
|
599
|
-
points: explicitRoutePoints,
|
|
600
|
-
durationSecs: travel.durationSecs,
|
|
601
|
-
arrivalThreshold: travel.arrivalThreshold,
|
|
602
|
-
onArrive: () => {
|
|
603
|
-
void context.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
|
|
604
|
-
...createRouteArrivalTransitionPayload(navigation.transitionTo),
|
|
605
|
-
onArrive,
|
|
606
|
-
}, {
|
|
607
|
-
source: id,
|
|
608
|
-
});
|
|
609
|
-
},
|
|
610
|
-
}, {
|
|
611
|
-
source: id,
|
|
612
|
-
})
|
|
613
|
-
: await context.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
|
|
614
|
-
.../** @type {Record<string, unknown>} */ (navigation.transitionTo),
|
|
615
|
-
onArrive,
|
|
616
|
-
}, {
|
|
617
|
-
source: id,
|
|
618
|
-
});
|
|
619
|
-
arrivalDeferred = hasActionResult(results);
|
|
620
|
-
}
|
|
621
|
-
if (Array.isArray(scene.preloadHints)) {
|
|
622
|
-
options.onPreloadHints?.(scene.preloadHints, scene, context);
|
|
623
|
-
}
|
|
624
|
-
options.onLayerState?.(scene, context);
|
|
625
|
-
if (!arrivalDeferred) {
|
|
626
|
-
onArrive();
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
/**
|
|
631
|
-
* @param {Record<string, unknown>} scene
|
|
632
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
633
|
-
* @param {unknown} event
|
|
634
|
-
* @param {() => void} onArrive
|
|
635
|
-
* @returns {Promise<boolean>} true when arrival will be reported asynchronously
|
|
636
|
-
*/
|
|
637
|
-
async function applyOrbitCameraScene(scene, context, event, onArrive) {
|
|
638
|
-
const camera = /** @type {Record<string, unknown>} */ (scene.camera);
|
|
639
|
-
const destinationOrbit = await resolveJourneyOrbit(camera, context);
|
|
640
|
-
if (!destinationOrbit) return false;
|
|
641
|
-
const lookTarget = await resolveJourneyTarget(camera.lookAt ?? camera.center, context)
|
|
642
|
-
?? destinationOrbit.center;
|
|
643
|
-
const source = { source: id };
|
|
644
|
-
await context.actions.invoke(SKYKIT_ACTIONS.navigation.cancel, null, source);
|
|
645
|
-
context.requestViewState({ targetPc: lookTarget }, id);
|
|
646
|
-
await context.actions.invoke(SKYKIT_ACTIONS.navigation.lockAt, {
|
|
647
|
-
...lookTarget,
|
|
648
|
-
...(destinationOrbit.normal ? { up: destinationOrbit.normal } : {}),
|
|
649
|
-
dwellSecs: resolveJourneyDwellSecs(camera, scene.travel),
|
|
650
|
-
recenterSpeed: 0.06,
|
|
651
|
-
}, source);
|
|
652
|
-
|
|
653
|
-
if (isInitialJourneyEvent(event)) {
|
|
654
|
-
const initialNormal = destinationOrbit.normal ?? { x: 0, y: 1, z: 0 };
|
|
655
|
-
const initialObserverPc = resolveInitialSceneObserverPc(scene)
|
|
656
|
-
?? defaultOrbitPosition(destinationOrbit.center, destinationOrbit.radius, initialNormal);
|
|
657
|
-
rememberResolvedJourneyOrbit(scene, {
|
|
658
|
-
...destinationOrbit,
|
|
659
|
-
normal: initialNormal,
|
|
660
|
-
});
|
|
661
|
-
context.requestViewState({
|
|
662
|
-
observerPc: initialObserverPc,
|
|
663
|
-
targetPc: lookTarget,
|
|
664
|
-
}, id);
|
|
665
|
-
await context.actions.invoke(SKYKIT_ACTIONS.navigation.orbit, {
|
|
666
|
-
center: destinationOrbit.center,
|
|
667
|
-
radius: destinationOrbit.radius,
|
|
668
|
-
angularSpeedRadPerSec: destinationOrbit.angularSpeedRadPerSec,
|
|
669
|
-
normal: initialNormal,
|
|
670
|
-
}, source);
|
|
671
|
-
return false;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
const travel = normalizeJourneySceneTravel(scene.travel);
|
|
675
|
-
const explicitRoutePoints = await resolveJourneyRoutePoints(scene, travel, context);
|
|
676
|
-
if (explicitRoutePoints.length >= 2) {
|
|
677
|
-
const arrivalAction = normalizeJourneyOrbitAction(
|
|
678
|
-
travel.arrivalAction ?? scene.travelPathArrivalAction,
|
|
679
|
-
destinationOrbit,
|
|
680
|
-
);
|
|
681
|
-
rememberResolvedJourneyOrbit(scene, arrivalAction);
|
|
682
|
-
const results = await context.actions.invoke(SKYKIT_ACTIONS.navigation.flyPolyline, {
|
|
683
|
-
points: explicitRoutePoints,
|
|
684
|
-
durationSecs: travel.durationSecs,
|
|
685
|
-
arrivalThreshold: travel.arrivalThreshold,
|
|
686
|
-
arrivalAction,
|
|
687
|
-
onArrive,
|
|
688
|
-
}, source);
|
|
689
|
-
return hasActionResult(results);
|
|
690
|
-
}
|
|
691
|
-
const route = createOrbitTransferRoute({
|
|
692
|
-
start: context.getViewState().observerPc,
|
|
693
|
-
sourceOrbit: await resolveSourceJourneyOrbit(event, context),
|
|
694
|
-
destinationOrbit,
|
|
695
|
-
durationSecs: travel.durationSecs,
|
|
696
|
-
sampleStepSecs: travel.sampleStepSecs,
|
|
697
|
-
});
|
|
698
|
-
if (route && Array.isArray(route.points) && route.points.length >= 2) {
|
|
699
|
-
rememberResolvedJourneyOrbit(scene, route.arrivalAction);
|
|
700
|
-
const results = await context.actions.invoke(SKYKIT_ACTIONS.navigation.flyPolyline, {
|
|
701
|
-
points: route.points,
|
|
702
|
-
durationSecs: travel.durationSecs,
|
|
703
|
-
currentSpeed: route.departureSpeed,
|
|
704
|
-
arrivalSpeed: route.arrivalSpeed,
|
|
705
|
-
arrivalThreshold: travel.arrivalThreshold,
|
|
706
|
-
arrivalAction: route.arrivalAction,
|
|
707
|
-
onArrive,
|
|
708
|
-
}, source);
|
|
709
|
-
return hasActionResult(results);
|
|
710
|
-
}
|
|
711
|
-
const fallbackNormal = destinationOrbit.normal ?? { x: 0, y: 1, z: 0 };
|
|
712
|
-
rememberResolvedJourneyOrbit(scene, {
|
|
713
|
-
...destinationOrbit,
|
|
714
|
-
normal: fallbackNormal,
|
|
715
|
-
});
|
|
716
|
-
await context.actions.invoke(SKYKIT_ACTIONS.navigation.orbit, {
|
|
717
|
-
center: destinationOrbit.center,
|
|
718
|
-
radius: destinationOrbit.radius,
|
|
719
|
-
angularSpeedRadPerSec: destinationOrbit.angularSpeedRadPerSec,
|
|
720
|
-
normal: fallbackNormal,
|
|
721
|
-
}, source);
|
|
722
|
-
return false;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* @param {unknown} transitionTo
|
|
727
|
-
* @returns {Record<string, unknown>}
|
|
728
|
-
*/
|
|
729
|
-
function createRouteArrivalTransitionPayload(transitionTo) {
|
|
730
|
-
const payload = /** @type {Record<string, unknown>} */ (
|
|
731
|
-
transitionTo && typeof transitionTo === 'object' ? transitionTo : {}
|
|
732
|
-
);
|
|
733
|
-
return {
|
|
734
|
-
...payload,
|
|
735
|
-
movement: {
|
|
736
|
-
...(payload.movement && typeof payload.movement === 'object'
|
|
737
|
-
? /** @type {Record<string, unknown>} */ (payload.movement)
|
|
738
|
-
: {}),
|
|
739
|
-
durationSecs: 0.001,
|
|
740
|
-
},
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
* @param {Record<string, unknown>} scene
|
|
746
|
-
* @param {Record<string, unknown>} travel
|
|
747
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
748
|
-
* @returns {Promise<Vector3Like[]>}
|
|
749
|
-
*/
|
|
750
|
-
async function resolveJourneyRoutePoints(scene, travel, context) {
|
|
751
|
-
const rawPoints = resolveJourneyRoutePointSource(scene, travel);
|
|
752
|
-
if (!rawPoints) return [];
|
|
753
|
-
const points = [];
|
|
754
|
-
for (const point of Array.from(rawPoints)) {
|
|
755
|
-
const resolved = await resolveJourneyTarget(point, context);
|
|
756
|
-
if (resolved) points.push(resolved);
|
|
757
|
-
}
|
|
758
|
-
return points;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* @param {Record<string, unknown>} scene
|
|
763
|
-
* @param {Record<string, unknown>} travel
|
|
764
|
-
* @returns {Iterable<unknown> | null}
|
|
765
|
-
*/
|
|
766
|
-
function resolveJourneyRoutePointSource(scene, travel) {
|
|
767
|
-
return firstIterable(
|
|
768
|
-
scene.travelPathPc,
|
|
769
|
-
scene.travelPath,
|
|
770
|
-
travel.pathPointsPc,
|
|
771
|
-
travel.pointsPc,
|
|
772
|
-
travel.points,
|
|
773
|
-
);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
* @param {...unknown} candidates
|
|
778
|
-
* @returns {Iterable<unknown> | null}
|
|
779
|
-
*/
|
|
780
|
-
function firstIterable(...candidates) {
|
|
781
|
-
for (const candidate of candidates) {
|
|
782
|
-
if (
|
|
783
|
-
candidate
|
|
784
|
-
&& typeof /** @type {{ [Symbol.iterator]?: unknown }} */ (candidate)[Symbol.iterator] === 'function'
|
|
785
|
-
) {
|
|
786
|
-
return /** @type {Iterable<unknown>} */ (candidate);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
return null;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* @param {unknown} value
|
|
794
|
-
* @param {{ center: Vector3Like; radius: number; angularSpeedRadPerSec: number; normal?: Vector3Like }} fallback
|
|
795
|
-
* @returns {{ type: 'orbit'; center: Vector3Like; radius: number; angularSpeedRadPerSec: number; normal: Vector3Like }}
|
|
796
|
-
*/
|
|
797
|
-
function normalizeJourneyOrbitAction(value, fallback) {
|
|
798
|
-
const source = value && typeof value === 'object'
|
|
799
|
-
? /** @type {Record<string, unknown>} */ (value)
|
|
800
|
-
: {};
|
|
801
|
-
const fallbackNormal = fallback.normal ?? { x: 0, y: 1, z: 0 };
|
|
802
|
-
return {
|
|
803
|
-
type: 'orbit',
|
|
804
|
-
center: normalizeOptionalVector3(source.center) ?? cloneVector3(fallback.center),
|
|
805
|
-
radius: positiveFinite(source.radius ?? source.radiusPc, fallback.radius),
|
|
806
|
-
angularSpeedRadPerSec: finiteNumber(
|
|
807
|
-
source.angularSpeedRadPerSec ?? source.angularSpeed,
|
|
808
|
-
fallback.angularSpeedRadPerSec,
|
|
809
|
-
),
|
|
810
|
-
normal: normalizeDirectionVector(source.normal, fallbackNormal),
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
/**
|
|
815
|
-
* @param {Record<string, unknown>} camera
|
|
816
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
817
|
-
*/
|
|
818
|
-
async function resolveJourneyOrbit(camera, context) {
|
|
819
|
-
const center = await resolveJourneyTarget(camera.center, context);
|
|
820
|
-
if (!center) return null;
|
|
821
|
-
return {
|
|
822
|
-
center,
|
|
823
|
-
radius: positiveFinite(camera.radiusPc, 1),
|
|
824
|
-
angularSpeedRadPerSec: finiteNumber(camera.angularSpeedRadPerSec, 0.1),
|
|
825
|
-
...(camera.normal != null ? { normal: normalizeDirectionVector(camera.normal, { x: 0, y: 1, z: 0 }) } : {}),
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
* @param {Record<string, unknown>} scene
|
|
831
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
832
|
-
* @param {unknown} event
|
|
833
|
-
*/
|
|
834
|
-
async function notifySceneArrive(scene, context, event) {
|
|
835
|
-
try {
|
|
836
|
-
await options.onSceneArrive?.(scene, context, event);
|
|
837
|
-
} catch (error) {
|
|
838
|
-
context.emit?.({
|
|
839
|
-
type: 'journey/scene-arrive/error',
|
|
840
|
-
pluginId: id,
|
|
841
|
-
message: 'Failed to handle journey scene arrival.',
|
|
842
|
-
error,
|
|
843
|
-
event,
|
|
844
|
-
});
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
/**
|
|
849
|
-
* @param {unknown} input
|
|
850
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
851
|
-
*/
|
|
852
|
-
async function resolveJourneyTarget(input, context) {
|
|
853
|
-
if (typeof input === 'string') {
|
|
854
|
-
const targets = resolveJourneyTargets();
|
|
855
|
-
const target = targets[input];
|
|
856
|
-
if (target && typeof target === 'object' && target.positionPc) {
|
|
857
|
-
return normalizeVector3(target.positionPc, { x: 0, y: 0, z: 0 });
|
|
858
|
-
}
|
|
859
|
-
return null;
|
|
860
|
-
}
|
|
861
|
-
return await resolveSpatialTarget(
|
|
862
|
-
/** @type {import('@found-in-space/spatial').SpatialTargetInput} */ (input),
|
|
863
|
-
{ observerPc: context.getViewState().observerPc },
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function resolveJourneyTargets() {
|
|
868
|
-
const graph = controller?.graph;
|
|
869
|
-
if (graph && typeof graph === 'object' && 'targets' in graph) {
|
|
870
|
-
return /** @type {Record<string, { positionPc?: unknown }>} */ (
|
|
871
|
-
/** @type {Record<string, unknown>} */ (graph).targets ?? {}
|
|
872
|
-
);
|
|
873
|
-
}
|
|
874
|
-
return /** @type {Record<string, { positionPc?: unknown }>} */ (options.journey?.targets ?? {});
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* @param {unknown} event
|
|
879
|
-
* @param {import('./index.d.ts').SkykitThreePluginContext} context
|
|
880
|
-
*/
|
|
881
|
-
async function resolveSourceJourneyOrbit(event, context) {
|
|
882
|
-
const previousSceneId = event && typeof event === 'object'
|
|
883
|
-
? /** @type {{ previousSceneId?: unknown }} */ (event).previousSceneId
|
|
884
|
-
: null;
|
|
885
|
-
if (typeof previousSceneId !== 'string') return null;
|
|
886
|
-
const resolved = resolvedJourneyOrbits.get(previousSceneId);
|
|
887
|
-
if (resolved) return resolved;
|
|
888
|
-
const previousScene = controller?.graph.getScene(previousSceneId);
|
|
889
|
-
const camera = previousScene?.camera && typeof previousScene.camera === 'object'
|
|
890
|
-
? /** @type {Record<string, unknown>} */ (previousScene.camera)
|
|
891
|
-
: null;
|
|
892
|
-
return camera && camera.type === 'orbit' ? resolveJourneyOrbit(camera, context) : null;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
/** @param {Record<string, unknown>} scene */
|
|
896
|
-
function resolveInitialSceneObserverPc(scene) {
|
|
897
|
-
const view = scene.view && typeof scene.view === 'object'
|
|
898
|
-
? /** @type {{ observerPc?: unknown }} */ (scene.view)
|
|
899
|
-
: null;
|
|
900
|
-
return normalizeOptionalVector3(view?.observerPc);
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* @param {Record<string, unknown>} scene
|
|
905
|
-
* @param {{ center: Vector3Like; radius: number; angularSpeedRadPerSec: number; normal: Vector3Like }} orbit
|
|
906
|
-
*/
|
|
907
|
-
function rememberResolvedJourneyOrbit(scene, orbit) {
|
|
908
|
-
const sceneId = typeof scene.sceneId === 'string' ? scene.sceneId : null;
|
|
909
|
-
if (!sceneId) return;
|
|
910
|
-
resolvedJourneyOrbits.set(sceneId, {
|
|
911
|
-
center: cloneVector3(orbit.center),
|
|
912
|
-
radius: orbit.radius,
|
|
913
|
-
angularSpeedRadPerSec: orbit.angularSpeedRadPerSec,
|
|
914
|
-
normal: cloneVector3(orbit.normal),
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* @param {import('@found-in-space/journey').TimedJourneyFrame} frameState
|
|
920
|
-
* @param {{ viewer: import('./index.d.ts').SkykitViewer; view: import('./index.d.ts').SkykitViewState }} frame
|
|
921
|
-
*/
|
|
922
|
-
function applyTimedFrame(frameState, frame) {
|
|
923
|
-
const context = pluginContext;
|
|
924
|
-
const hookResult = options.applyFrame?.(frameState, context, frame);
|
|
925
|
-
if (hookResult === false) return;
|
|
926
|
-
frame.viewer.requestViewState({
|
|
927
|
-
observerPc: frameState.observerPc,
|
|
928
|
-
orientationIcrs: frameState.orientationIcrs,
|
|
929
|
-
targetPc: frameState.targetPc,
|
|
930
|
-
motion: {
|
|
931
|
-
velocityPcPerSec: frameState.velocityPcPerSec,
|
|
932
|
-
speedPcPerSec: frameState.speedPcPerSec,
|
|
933
|
-
},
|
|
934
|
-
}, id);
|
|
935
|
-
if (frameState.cue && frameState.cue.id !== lastCueId) {
|
|
936
|
-
lastCueId = frameState.cue.id;
|
|
937
|
-
options.onCue?.(frameState.cue, frameState, context);
|
|
938
|
-
} else if (!frameState.cue) {
|
|
939
|
-
lastCueId = null;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function getSnapshot() {
|
|
944
|
-
return {
|
|
945
|
-
id,
|
|
946
|
-
disposed,
|
|
947
|
-
playing,
|
|
948
|
-
currentTimeSecs,
|
|
949
|
-
initialSceneApplied,
|
|
950
|
-
timedPreloadHintsEmitted,
|
|
951
|
-
controller: controller?.getSnapshot?.() ?? null,
|
|
952
|
-
timedJourney: evaluator
|
|
953
|
-
? {
|
|
954
|
-
durationSecs: evaluator.durationSecs,
|
|
955
|
-
journeyId: evaluator.journey.id,
|
|
956
|
-
}
|
|
957
|
-
: null,
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
405
|
|
|
962
406
|
/**
|
|
963
407
|
* Strategy-only convenience helper. View-bound lookahead hints stay in preload
|
|
@@ -1550,108 +994,6 @@ function getDefaultEventTarget() {
|
|
|
1550
994
|
return typeof globalThis.addEventListener === 'function' ? globalThis : null;
|
|
1551
995
|
}
|
|
1552
996
|
|
|
1553
|
-
/** @param {Record<string, unknown>} scene */
|
|
1554
|
-
function isOrbitCameraScene(scene) {
|
|
1555
|
-
return Boolean(
|
|
1556
|
-
scene.camera
|
|
1557
|
-
&& typeof scene.camera === 'object'
|
|
1558
|
-
&& /** @type {{ type?: unknown }} */ (scene.camera).type === 'orbit',
|
|
1559
|
-
);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
/** @param {unknown} event */
|
|
1563
|
-
function isInitialJourneyEvent(event) {
|
|
1564
|
-
return Boolean(
|
|
1565
|
-
event
|
|
1566
|
-
&& typeof event === 'object'
|
|
1567
|
-
&& /** @type {{ type?: unknown; previousSceneId?: unknown }} */ (event).type === 'journey/initial',
|
|
1568
|
-
);
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
/**
|
|
1572
|
-
* @param {unknown} value
|
|
1573
|
-
* @returns {Record<string, unknown> & { durationSecs: number; sampleStepSecs: number; arrivalThreshold: number }}
|
|
1574
|
-
*/
|
|
1575
|
-
function normalizeJourneySceneTravel(value) {
|
|
1576
|
-
const source = /** @type {Record<string, unknown>} */ (
|
|
1577
|
-
value && typeof value === 'object' ? value : {}
|
|
1578
|
-
);
|
|
1579
|
-
return {
|
|
1580
|
-
...source,
|
|
1581
|
-
durationSecs: positiveFinite(source.durationSecs, 5),
|
|
1582
|
-
sampleStepSecs: positiveFinite(source.sampleStepSecs, 1 / 60),
|
|
1583
|
-
arrivalThreshold: positiveFinite(source.arrivalThreshold, 0.05),
|
|
1584
|
-
};
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
/**
|
|
1588
|
-
* @param {Record<string, unknown>} camera
|
|
1589
|
-
* @param {unknown} travel
|
|
1590
|
-
*/
|
|
1591
|
-
function resolveJourneyDwellSecs(camera, travel) {
|
|
1592
|
-
if (camera.dwellSecs != null) return Math.max(0, finiteNumber(camera.dwellSecs, 0));
|
|
1593
|
-
if (travel && typeof travel === 'object' && 'dwellSecs' in travel) {
|
|
1594
|
-
return Math.max(0, finiteNumber(/** @type {{ dwellSecs?: unknown }} */ (travel).dwellSecs, 0));
|
|
1595
|
-
}
|
|
1596
|
-
return 0;
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
/**
|
|
1600
|
-
* @param {Vector3Like} center
|
|
1601
|
-
* @param {number} radius
|
|
1602
|
-
* @param {Vector3Like} normal
|
|
1603
|
-
* @returns {Vector3Like}
|
|
1604
|
-
*/
|
|
1605
|
-
function defaultOrbitPosition(center, radius, normal) {
|
|
1606
|
-
const axis = Math.abs(normal.x) < 0.9 ? { x: 1, y: 0, z: 0 } : { x: 0, y: 1, z: 0 };
|
|
1607
|
-
const projected = projectOnPlane(axis, normal);
|
|
1608
|
-
const length = Math.hypot(projected.x, projected.y, projected.z);
|
|
1609
|
-
const direction = length > 1e-9
|
|
1610
|
-
? { x: projected.x / length, y: projected.y / length, z: projected.z / length }
|
|
1611
|
-
: { x: 1, y: 0, z: 0 };
|
|
1612
|
-
return {
|
|
1613
|
-
x: center.x + direction.x * radius,
|
|
1614
|
-
y: center.y + direction.y * radius,
|
|
1615
|
-
z: center.z + direction.z * radius,
|
|
1616
|
-
};
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
/**
|
|
1620
|
-
* @param {unknown} value
|
|
1621
|
-
* @param {Vector3Like} fallback
|
|
1622
|
-
* @returns {Vector3Like}
|
|
1623
|
-
*/
|
|
1624
|
-
function normalizeDirectionVector(value, fallback) {
|
|
1625
|
-
const vector = normalizeVector3(value, fallback);
|
|
1626
|
-
const length = Math.hypot(vector.x, vector.y, vector.z);
|
|
1627
|
-
return length > 1e-9
|
|
1628
|
-
? { x: vector.x / length, y: vector.y / length, z: vector.z / length }
|
|
1629
|
-
: cloneVector3(fallback);
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
/** @param {unknown} value */
|
|
1633
|
-
function normalizeOptionalVector3(value) {
|
|
1634
|
-
if (!value || typeof value !== 'object') return null;
|
|
1635
|
-
const vector = /** @type {{ x?: unknown; y?: unknown; z?: unknown }} */ (value);
|
|
1636
|
-
const x = Number(vector.x);
|
|
1637
|
-
const y = Number(vector.y);
|
|
1638
|
-
const z = Number(vector.z);
|
|
1639
|
-
return [x, y, z].every(Number.isFinite) ? { x, y, z } : null;
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
/**
|
|
1643
|
-
* @param {Vector3Like} vector
|
|
1644
|
-
* @param {Vector3Like} normal
|
|
1645
|
-
* @returns {Vector3Like}
|
|
1646
|
-
*/
|
|
1647
|
-
function projectOnPlane(vector, normal) {
|
|
1648
|
-
const amount = vector.x * normal.x + vector.y * normal.y + vector.z * normal.z;
|
|
1649
|
-
return {
|
|
1650
|
-
x: vector.x - normal.x * amount,
|
|
1651
|
-
y: vector.y - normal.y * amount,
|
|
1652
|
-
z: vector.z - normal.z * amount,
|
|
1653
|
-
};
|
|
1654
|
-
}
|
|
1655
997
|
|
|
1656
998
|
/**
|
|
1657
999
|
* @param {unknown} payload
|
|
@@ -1690,11 +1032,6 @@ function resolveOnArrive(payload) {
|
|
|
1690
1032
|
: null;
|
|
1691
1033
|
}
|
|
1692
1034
|
|
|
1693
|
-
/** @param {PromiseSettledResult<unknown>[]} results */
|
|
1694
|
-
function hasActionResult(results) {
|
|
1695
|
-
return results.some((result) => result.status === 'fulfilled' && result.value != null);
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
1035
|
/** @param {Record<string, unknown>} targetSource */
|
|
1699
1036
|
function resolveTransitionPositionInput(targetSource) {
|
|
1700
1037
|
if (targetSource.observerPc !== undefined) return targetSource.observerPc;
|
|
@@ -1717,29 +1054,6 @@ function isSpatialTargetLike(value) {
|
|
|
1717
1054
|
return Array.isArray(value) && value.length >= 3;
|
|
1718
1055
|
}
|
|
1719
1056
|
|
|
1720
|
-
/** @param {unknown} payload */
|
|
1721
|
-
function resolveSceneId(payload) {
|
|
1722
|
-
if (typeof payload === 'string') return payload;
|
|
1723
|
-
if (!payload || typeof payload !== 'object') return null;
|
|
1724
|
-
const source = /** @type {Record<string, unknown>} */ (payload);
|
|
1725
|
-
const value = source.chapterId ?? source.sceneId ?? source.id;
|
|
1726
|
-
return typeof value === 'string' ? value : null;
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
/** @param {unknown} payload */
|
|
1730
|
-
function resolveTimeSecs(payload) {
|
|
1731
|
-
if (Number.isFinite(Number(payload))) return Number(payload);
|
|
1732
|
-
if (!payload || typeof payload !== 'object') return 0;
|
|
1733
|
-
const value = /** @type {Record<string, unknown>} */ (payload).timeSecs
|
|
1734
|
-
?? /** @type {Record<string, unknown>} */ (payload).sceneTimeSecs
|
|
1735
|
-
?? /** @type {Record<string, unknown>} */ (payload).time;
|
|
1736
|
-
return finiteNumber(value, 0);
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
/** @param {number} timeSecs @param {number} durationSecs */
|
|
1740
|
-
function clampTime(timeSecs, durationSecs) {
|
|
1741
|
-
return Math.min(Math.max(0, finiteNumber(timeSecs, 0)), Math.max(0, finiteNumber(durationSecs, 0)));
|
|
1742
|
-
}
|
|
1743
1057
|
|
|
1744
1058
|
/**
|
|
1745
1059
|
* @param {unknown} value
|