@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
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
|
-
import { createJourney } from '@found-in-space/journey';
|
|
5
4
|
import {
|
|
6
5
|
createObserverShellStrategy,
|
|
7
6
|
createStarCellData,
|
|
8
7
|
encodeMorton3D,
|
|
9
8
|
} from '@found-in-space/star-trees';
|
|
10
9
|
|
|
10
|
+
import {
|
|
11
|
+
resolveSpatialTarget,
|
|
12
|
+
} from '@found-in-space/spatial';
|
|
11
13
|
import {
|
|
12
14
|
SKYKIT_ACTION_NAMESPACE,
|
|
13
15
|
SKYKIT_ACTIONS,
|
|
@@ -18,12 +20,12 @@ import {
|
|
|
18
20
|
createDesktopSkykitObserverRig,
|
|
19
21
|
createObject3dLayer,
|
|
20
22
|
createObject3dPlugin,
|
|
23
|
+
createRaDecLookAt,
|
|
21
24
|
createMouseLookPlugin,
|
|
22
25
|
createSkyGrabPlugin,
|
|
23
26
|
createSkykitDefaultKeyboardNavigationBindings,
|
|
24
27
|
createSkykitAnimationLoop,
|
|
25
28
|
createSkykitDebugBridge,
|
|
26
|
-
createSkykitJourneyPlugin,
|
|
27
29
|
createSkykitNavigationPlugin,
|
|
28
30
|
createSkykitStarPreloadRequestsFromSpatialHints,
|
|
29
31
|
createSkykitStarStrategiesFromSpatialHints,
|
|
@@ -36,6 +38,7 @@ import {
|
|
|
36
38
|
createStreamingStarLayer,
|
|
37
39
|
createStreamingStarsPlugin,
|
|
38
40
|
installSkykitDebugGlobal,
|
|
41
|
+
parseSpatialLookAtText,
|
|
39
42
|
} from '../index.js';
|
|
40
43
|
|
|
41
44
|
function createHost() {
|
|
@@ -143,10 +146,6 @@ function assertStrategyBehavior(strategy) {
|
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
function distance(a, b) {
|
|
147
|
-
return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
149
|
test('createSkykitViewer creates roots, mounts renderer, runs lifecycle, and disposes cleanly', async () => {
|
|
151
150
|
const host = createHost();
|
|
152
151
|
const renderer = createRenderer();
|
|
@@ -319,6 +318,50 @@ test('viewer derives camera orientation from lookAt targets, sky coordinates, an
|
|
|
319
318
|
assertVectorApprox(localVectorFromView(view, { x: 0, y: 1, z: 0 }), { x: 0, y: 1, z: 0 });
|
|
320
319
|
await skyViewer.dispose();
|
|
321
320
|
|
|
321
|
+
const alnilamViewer = await createSkykitViewer({
|
|
322
|
+
renderer: createRenderer(),
|
|
323
|
+
view: {
|
|
324
|
+
lookAt: createRaDecLookAt('05h 36m 12.81s', '−01° 12′ 06.9″'),
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
view = alnilamViewer.getViewState();
|
|
328
|
+
assertVectorApprox(
|
|
329
|
+
localVectorFromView(view, { x: 0, y: 0, z: -1 }),
|
|
330
|
+
directionFromRaDec(84.053375, -1.2019166666666667),
|
|
331
|
+
);
|
|
332
|
+
await alnilamViewer.dispose();
|
|
333
|
+
|
|
334
|
+
const siriusSpec = parseSpatialLookAtText('06h 45m 08.9s, -16d 42m 58s, 2.64pc');
|
|
335
|
+
const orionSpec = parseSpatialLookAtText('05h 35m 17.3s, -05d 23m 28s, 414pc');
|
|
336
|
+
const siriusPc = resolveSpatialTarget(siriusSpec);
|
|
337
|
+
const orionPc = resolveSpatialTarget(orionSpec);
|
|
338
|
+
assert.ok(siriusPc && orionPc && orionSpec);
|
|
339
|
+
const solarTargetViewer = await createSkykitViewer({
|
|
340
|
+
renderer: createRenderer(),
|
|
341
|
+
view: {
|
|
342
|
+
observerPc: siriusPc,
|
|
343
|
+
lookAt: orionSpec,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
view = solarTargetViewer.getViewState();
|
|
347
|
+
assertVectorApprox(view.observerPc, siriusPc);
|
|
348
|
+
assertVectorApprox(view.targetPc, orionPc);
|
|
349
|
+
assertVectorApprox(
|
|
350
|
+
localVectorFromView(view, { x: 0, y: 0, z: -1 }),
|
|
351
|
+
normalizeVector(subtractVectors(orionPc, siriusPc)),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const movedObserver = { x: -8, y: 3, z: 11 };
|
|
355
|
+
solarTargetViewer.requestViewState({ observerPc: movedObserver, lookAt: orionSpec }, 'test-solar-radec');
|
|
356
|
+
solarTargetViewer.update(0);
|
|
357
|
+
view = solarTargetViewer.getViewState();
|
|
358
|
+
assertVectorApprox(view.targetPc, orionPc);
|
|
359
|
+
assertVectorApprox(
|
|
360
|
+
localVectorFromView(view, { x: 0, y: 0, z: -1 }),
|
|
361
|
+
normalizeVector(subtractVectors(orionPc, movedObserver)),
|
|
362
|
+
);
|
|
363
|
+
await solarTargetViewer.dispose();
|
|
364
|
+
|
|
322
365
|
const starViewer = await createSkykitViewer({
|
|
323
366
|
renderer: createRenderer(),
|
|
324
367
|
view: { lookAt: { star: 'hyades' } },
|
|
@@ -429,15 +472,15 @@ test('action registry registers contexts, invokes multiple handlers, and reports
|
|
|
429
472
|
offLow();
|
|
430
473
|
assert.equal(registry.listActions().find((entry) => entry.id === 'lesson:demo.run')?.handlerCount, 2);
|
|
431
474
|
|
|
432
|
-
const
|
|
433
|
-
|
|
475
|
+
const offChapters = registry.registerContext('lesson:chapters', {
|
|
476
|
+
goTo({ payload }) {
|
|
434
477
|
calls.push(`chapter:${payload}`);
|
|
435
478
|
},
|
|
436
479
|
});
|
|
437
|
-
await registry.invoke(
|
|
480
|
+
await registry.invoke('lesson:chapters.goTo', 'intro');
|
|
438
481
|
assert.equal(calls.at(-1), 'chapter:intro');
|
|
439
|
-
|
|
440
|
-
assert.equal(registry.listActions().some((entry) => entry.id ===
|
|
482
|
+
offChapters();
|
|
483
|
+
assert.equal(registry.listActions().some((entry) => entry.id === 'lesson:chapters.goTo'), false);
|
|
441
484
|
});
|
|
442
485
|
|
|
443
486
|
test('action registry tracks held action sources and control values', () => {
|
|
@@ -1533,7 +1576,7 @@ test('navigation transition action restores pose with independent lane durations
|
|
|
1533
1576
|
});
|
|
1534
1577
|
viewer.update(1);
|
|
1535
1578
|
viewer.update(0);
|
|
1536
|
-
assert.deepEqual(viewer.getViewState().observerPc, { x:
|
|
1579
|
+
assert.deepEqual(viewer.getViewState().observerPc, { x: 10, y: 0, z: 0 });
|
|
1537
1580
|
assert.deepEqual(viewer.getViewState().orientationIcrs, orientationAfterExplicitTransition);
|
|
1538
1581
|
|
|
1539
1582
|
await viewer.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
|
|
@@ -1545,504 +1588,20 @@ test('navigation transition action restores pose with independent lane durations
|
|
|
1545
1588
|
assert.deepEqual(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 });
|
|
1546
1589
|
assert.deepEqual(viewer.getViewState().orientationIcrs, orientationAfterExplicitTransition);
|
|
1547
1590
|
|
|
1548
|
-
await viewer.
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
test('journey plugin registers actions, applies scenes, timed frames, and preload hooks', async () => {
|
|
1552
|
-
const preloadEvents = [];
|
|
1553
|
-
const cueEvents = [];
|
|
1554
|
-
const journeyPlugin = createSkykitJourneyPlugin({
|
|
1555
|
-
scenes: {
|
|
1556
|
-
intro: { title: 'Intro', view: { limitingMagnitude: 5 } },
|
|
1557
|
-
hyades: {
|
|
1558
|
-
title: 'Hyades',
|
|
1559
|
-
view: { observerPc: { x: 1, y: 2, z: 3 } },
|
|
1560
|
-
preloadHints: [
|
|
1561
|
-
{ kind: 'sphere-volume', centerPc: { x: 1, y: 2, z: 3 }, radiusPc: 4 },
|
|
1562
|
-
],
|
|
1563
|
-
},
|
|
1564
|
-
},
|
|
1565
|
-
initialSceneId: 'intro',
|
|
1566
|
-
timedJourney: {
|
|
1567
|
-
durationSecs: 2,
|
|
1568
|
-
locationWaypoints: [
|
|
1569
|
-
{ id: 'a', timeSecs: 0, positionPc: { x: 0, y: 0, z: 0 } },
|
|
1570
|
-
{ id: 'b', timeSecs: 2, positionPc: { x: 2, y: 0, z: 0 } },
|
|
1571
|
-
],
|
|
1572
|
-
cameraLookWaypoints: [
|
|
1573
|
-
{ id: 'look', timeSecs: 0, kind: 'direction', forward: { x: 1, y: 0, z: 0 } },
|
|
1574
|
-
],
|
|
1575
|
-
cues: [{ id: 'cue', startSecs: 0, endSecs: 2 }],
|
|
1576
|
-
},
|
|
1577
|
-
onPreloadHints: (hints) => preloadEvents.push(hints),
|
|
1578
|
-
onCue: (cue) => cueEvents.push(cue.id),
|
|
1579
|
-
});
|
|
1580
|
-
const viewer = await createSkykitViewer({
|
|
1581
|
-
renderer: createRenderer(),
|
|
1582
|
-
plugins: [journeyPlugin],
|
|
1583
|
-
});
|
|
1584
|
-
|
|
1585
|
-
viewer.update(0);
|
|
1586
|
-
assert.equal(viewer.getViewState().limitingMagnitude, 5);
|
|
1587
|
-
assert.equal(journeyPlugin.getSnapshot().initialSceneApplied, true);
|
|
1588
|
-
assert.equal(journeyPlugin.getSnapshot().timedPreloadHintsEmitted, true);
|
|
1589
|
-
|
|
1590
|
-
assert.equal(viewer.actions.listActions().some((entry) => entry.id === SKYKIT_ACTIONS.journey.goToChapter), true);
|
|
1591
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'hyades');
|
|
1592
|
-
viewer.update(0);
|
|
1593
|
-
assert.deepEqual(viewer.getViewState().observerPc, { x: 1, y: 2, z: 3 });
|
|
1594
|
-
assert.equal(preloadEvents.length, 1);
|
|
1595
|
-
|
|
1596
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.seek, { timeSecs: 1 });
|
|
1597
|
-
viewer.update(0);
|
|
1598
|
-
assert.ok(viewer.getViewState().observerPc.x > 0);
|
|
1599
|
-
assert.deepEqual(cueEvents, ['cue']);
|
|
1600
|
-
|
|
1601
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.play);
|
|
1602
|
-
const beforePlayX = viewer.getViewState().observerPc.x;
|
|
1603
|
-
viewer.update(0.5);
|
|
1604
|
-
viewer.update(0);
|
|
1605
|
-
assert.ok(viewer.getViewState().observerPc.x > beforePlayX);
|
|
1606
|
-
|
|
1607
|
-
await viewer.dispose();
|
|
1608
|
-
});
|
|
1609
|
-
|
|
1610
|
-
test('journey plugin executes semantic orbit-transfer scenes without snapping', async () => {
|
|
1611
|
-
const navigationPlugin = createSkykitNavigationPlugin({ speed: 20, acceleration: 20, deceleration: 20 });
|
|
1612
|
-
const journey = createJourney({
|
|
1613
|
-
initial: 'inside',
|
|
1614
|
-
order: ['inside', 'outside', 'hyades', 'free'],
|
|
1615
|
-
targets: {
|
|
1616
|
-
sun: { positionPc: { x: 0, y: 0, z: 0 } },
|
|
1617
|
-
hyades: { positionPc: { x: 18, y: 42, z: 7 } },
|
|
1618
|
-
free: { positionPc: { x: -20, y: 35, z: 12 } },
|
|
1619
|
-
orion: { positionPc: { x: 0, y: 0, z: -100 } },
|
|
1620
|
-
},
|
|
1621
|
-
scenes: {
|
|
1622
|
-
inside: {
|
|
1623
|
-
view: {
|
|
1624
|
-
observerPc: { x: 0, y: 4, z: 0 },
|
|
1625
|
-
},
|
|
1626
|
-
camera: {
|
|
1627
|
-
type: 'orbit',
|
|
1628
|
-
center: 'sun',
|
|
1629
|
-
radiusPc: 4,
|
|
1630
|
-
angularSpeedRadPerSec: 0.4,
|
|
1631
|
-
lookAt: 'orion',
|
|
1632
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1633
|
-
},
|
|
1634
|
-
},
|
|
1635
|
-
outside: {
|
|
1636
|
-
camera: {
|
|
1637
|
-
type: 'orbit',
|
|
1638
|
-
center: 'sun',
|
|
1639
|
-
radiusPc: 10,
|
|
1640
|
-
angularSpeedRadPerSec: 0.2,
|
|
1641
|
-
lookAt: 'sun',
|
|
1642
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1643
|
-
},
|
|
1644
|
-
},
|
|
1645
|
-
hyades: {
|
|
1646
|
-
camera: {
|
|
1647
|
-
type: 'orbit',
|
|
1648
|
-
center: 'hyades',
|
|
1649
|
-
radiusPc: 6,
|
|
1650
|
-
angularSpeedRadPerSec: 0.3,
|
|
1651
|
-
lookAt: 'hyades',
|
|
1652
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1653
|
-
},
|
|
1654
|
-
},
|
|
1655
|
-
free: {
|
|
1656
|
-
camera: {
|
|
1657
|
-
type: 'orbit',
|
|
1658
|
-
center: 'free',
|
|
1659
|
-
radiusPc: 5,
|
|
1660
|
-
angularSpeedRadPerSec: 0.25,
|
|
1661
|
-
lookAt: 'free',
|
|
1662
|
-
},
|
|
1663
|
-
},
|
|
1664
|
-
},
|
|
1665
|
-
travel: { type: 'orbit-transfer', durationSecs: 2, sampleStepSecs: 0.25 },
|
|
1666
|
-
});
|
|
1667
|
-
const viewer = await createSkykitViewer({
|
|
1668
|
-
renderer: createRenderer(),
|
|
1669
|
-
plugins: [
|
|
1670
|
-
navigationPlugin,
|
|
1671
|
-
createSkykitJourneyPlugin({ journey }),
|
|
1672
|
-
],
|
|
1673
|
-
});
|
|
1674
|
-
|
|
1675
|
-
await flushMicrotasks();
|
|
1676
|
-
viewer.update(0);
|
|
1677
|
-
assert.deepEqual(viewer.getViewState().observerPc, { x: 0, y: 4, z: 0 });
|
|
1678
|
-
viewer.update(0.001);
|
|
1679
|
-
viewer.update(0);
|
|
1680
|
-
assert.ok(Math.abs(distance(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 }) - 4) < 1e-9);
|
|
1681
|
-
|
|
1682
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'outside');
|
|
1683
|
-
await flushMicrotasks();
|
|
1684
|
-
viewer.update(1);
|
|
1685
|
-
viewer.update(0);
|
|
1686
|
-
const halfwayRadius = distance(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 });
|
|
1687
|
-
assert.ok(halfwayRadius > 4);
|
|
1688
|
-
assert.ok(halfwayRadius < 10);
|
|
1689
|
-
|
|
1690
|
-
viewer.update(1.1);
|
|
1691
|
-
viewer.update(0);
|
|
1692
|
-
const arrival = { ...viewer.getViewState().observerPc };
|
|
1693
|
-
assert.ok(Math.abs(distance(arrival, { x: 0, y: 0, z: 0 }) - 10) < 1e-9);
|
|
1694
|
-
assert.equal(navigationPlugin.getSnapshot().navigation.activeAutomation, 'orbit');
|
|
1695
|
-
viewer.update(0.25);
|
|
1696
|
-
viewer.update(0);
|
|
1697
|
-
assert.ok(Math.abs(distance(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 }) - 10) < 1e-9);
|
|
1698
|
-
assert.notDeepEqual(viewer.getViewState().observerPc, arrival);
|
|
1699
|
-
|
|
1700
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'hyades');
|
|
1701
|
-
await flushMicrotasks();
|
|
1702
|
-
let handoff = null;
|
|
1703
|
-
let previous = { ...viewer.getViewState().observerPc };
|
|
1704
|
-
for (let index = 0; index < 120; index += 1) {
|
|
1705
|
-
viewer.update(1 / 30);
|
|
1706
|
-
viewer.update(0);
|
|
1707
|
-
const current = { ...viewer.getViewState().observerPc };
|
|
1708
|
-
const step = distance(previous, current);
|
|
1709
|
-
if (!handoff && navigationPlugin.getSnapshot().navigation.activeAutomation === 'orbit') {
|
|
1710
|
-
handoff = { position: current, step };
|
|
1711
|
-
}
|
|
1712
|
-
previous = current;
|
|
1713
|
-
}
|
|
1714
|
-
assert.ok(handoff);
|
|
1715
|
-
assert.ok(Math.abs(handoff.position.z - 7) < 1e-6);
|
|
1716
|
-
assert.ok(handoff.step < 0.5);
|
|
1717
|
-
|
|
1718
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'free');
|
|
1719
|
-
await flushMicrotasks();
|
|
1720
|
-
for (let index = 0; index < 120; index += 1) {
|
|
1721
|
-
viewer.update(1 / 30);
|
|
1722
|
-
viewer.update(0);
|
|
1723
|
-
}
|
|
1724
|
-
const freeAutomation = navigationPlugin.getSnapshot().navigation.movementAutomation;
|
|
1725
|
-
assert.equal(navigationPlugin.getSnapshot().navigation.activeAutomation, 'orbit');
|
|
1726
|
-
assert.ok(freeAutomation?.normal);
|
|
1727
|
-
assert.ok(Math.abs(freeAutomation.normal.z - 1) > 0.001);
|
|
1728
|
-
assert.ok(Math.abs(distance(viewer.getViewState().observerPc, { x: -20, y: 35, z: 12 }) - 5) < 1e-9);
|
|
1729
|
-
|
|
1730
|
-
await viewer.dispose();
|
|
1731
|
-
});
|
|
1732
|
-
|
|
1733
|
-
test('journey plugin uses explicit route points for authored orbit transfers', async () => {
|
|
1734
|
-
const navigationPlugin = createSkykitNavigationPlugin({ speed: 80, acceleration: 80, deceleration: 80 });
|
|
1735
|
-
const authoredPoints = [
|
|
1736
|
-
{ x: 0, y: 4, z: 0 },
|
|
1737
|
-
{ x: 40, y: 10, z: 80 },
|
|
1738
|
-
{ x: 100, y: 0, z: 10 },
|
|
1739
|
-
];
|
|
1740
|
-
const journey = createJourney({
|
|
1741
|
-
initial: 'inside',
|
|
1742
|
-
order: ['inside', 'omega'],
|
|
1743
|
-
targets: {
|
|
1744
|
-
sun: { positionPc: { x: 0, y: 0, z: 0 } },
|
|
1745
|
-
omega: { positionPc: { x: 100, y: 0, z: 0 } },
|
|
1746
|
-
},
|
|
1747
|
-
scenes: {
|
|
1748
|
-
inside: {
|
|
1749
|
-
view: { observerPc: { x: 0, y: 4, z: 0 } },
|
|
1750
|
-
camera: {
|
|
1751
|
-
type: 'orbit',
|
|
1752
|
-
center: 'sun',
|
|
1753
|
-
radiusPc: 4,
|
|
1754
|
-
angularSpeedRadPerSec: 0.2,
|
|
1755
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1756
|
-
},
|
|
1757
|
-
},
|
|
1758
|
-
omega: {
|
|
1759
|
-
camera: {
|
|
1760
|
-
type: 'orbit',
|
|
1761
|
-
center: 'omega',
|
|
1762
|
-
radiusPc: 10,
|
|
1763
|
-
angularSpeedRadPerSec: 0.12,
|
|
1764
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1765
|
-
},
|
|
1766
|
-
},
|
|
1767
|
-
},
|
|
1768
|
-
transitions: [
|
|
1769
|
-
{
|
|
1770
|
-
fromSceneId: 'inside',
|
|
1771
|
-
toSceneId: 'omega',
|
|
1772
|
-
travel: {
|
|
1773
|
-
type: 'orbit-transfer',
|
|
1774
|
-
durationSecs: 2,
|
|
1775
|
-
pointsPc: authoredPoints,
|
|
1776
|
-
arrivalAction: {
|
|
1777
|
-
type: 'orbit',
|
|
1778
|
-
center: { x: 100, y: 0, z: 0 },
|
|
1779
|
-
radius: 10,
|
|
1780
|
-
angularSpeedRadPerSec: 0.12,
|
|
1781
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1782
|
-
},
|
|
1783
|
-
},
|
|
1784
|
-
},
|
|
1785
|
-
],
|
|
1786
|
-
});
|
|
1787
|
-
const viewer = await createSkykitViewer({
|
|
1788
|
-
renderer: createRenderer(),
|
|
1789
|
-
plugins: [
|
|
1790
|
-
navigationPlugin,
|
|
1791
|
-
createSkykitJourneyPlugin({ journey }),
|
|
1792
|
-
],
|
|
1793
|
-
});
|
|
1794
|
-
|
|
1795
|
-
await flushMicrotasks();
|
|
1796
|
-
viewer.update(0);
|
|
1797
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'omega');
|
|
1798
|
-
await flushMicrotasks();
|
|
1799
|
-
|
|
1800
|
-
viewer.update(1);
|
|
1801
|
-
viewer.update(0);
|
|
1802
|
-
assert.ok(viewer.getViewState().observerPc.z > 20);
|
|
1803
|
-
|
|
1804
|
-
viewer.update(1.1);
|
|
1805
|
-
viewer.update(0);
|
|
1806
|
-
assert.equal(navigationPlugin.getSnapshot().navigation.activeAutomation, 'orbit');
|
|
1807
|
-
assert.ok(Math.abs(distance(viewer.getViewState().observerPc, { x: 100, y: 0, z: 0 }) - 10) < 1e-9);
|
|
1808
|
-
|
|
1809
|
-
await viewer.dispose();
|
|
1810
|
-
});
|
|
1811
|
-
|
|
1812
|
-
test('journey plugin can route to non-orbit scenes before applying the arrival transition', async () => {
|
|
1813
|
-
const navigationPlugin = createSkykitNavigationPlugin({ speed: 80, acceleration: 80, deceleration: 80 });
|
|
1814
|
-
const journey = createJourney({
|
|
1815
|
-
initial: 'omega',
|
|
1816
|
-
order: ['omega', 'local'],
|
|
1817
|
-
targets: {
|
|
1818
|
-
omega: { positionPc: { x: 100, y: 0, z: 0 } },
|
|
1819
|
-
},
|
|
1820
|
-
scenes: {
|
|
1821
|
-
omega: {
|
|
1822
|
-
view: { observerPc: { x: 100, y: 0, z: 10 } },
|
|
1823
|
-
camera: {
|
|
1824
|
-
type: 'orbit',
|
|
1825
|
-
center: 'omega',
|
|
1826
|
-
radiusPc: 10,
|
|
1827
|
-
angularSpeedRadPerSec: 0.12,
|
|
1828
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1829
|
-
},
|
|
1830
|
-
},
|
|
1831
|
-
local: {
|
|
1832
|
-
view: { lookAt: { targetPc: { x: 1, y: 0, z: 0 } } },
|
|
1833
|
-
navigation: {
|
|
1834
|
-
transitionTo: {
|
|
1835
|
-
observerPc: { x: 0, y: 0, z: 0 },
|
|
1836
|
-
orientationIcrs: { x: 0, y: 0, z: 0, w: 1 },
|
|
1837
|
-
durationSecs: 1,
|
|
1838
|
-
movement: { durationSecs: 1 },
|
|
1839
|
-
orientationTransition: { durationSecs: 1 },
|
|
1840
|
-
},
|
|
1841
|
-
},
|
|
1842
|
-
},
|
|
1843
|
-
},
|
|
1844
|
-
transitions: [
|
|
1845
|
-
{
|
|
1846
|
-
fromSceneId: 'omega',
|
|
1847
|
-
toSceneId: 'local',
|
|
1848
|
-
travel: {
|
|
1849
|
-
type: 'orbit-transfer',
|
|
1850
|
-
durationSecs: 2,
|
|
1851
|
-
pointsPc: [
|
|
1852
|
-
{ x: 100, y: 0, z: 10 },
|
|
1853
|
-
{ x: 50, y: 0, z: 80 },
|
|
1854
|
-
{ x: 0, y: 0, z: 0 },
|
|
1855
|
-
],
|
|
1856
|
-
},
|
|
1857
|
-
},
|
|
1858
|
-
],
|
|
1859
|
-
});
|
|
1860
|
-
const viewer = await createSkykitViewer({
|
|
1861
|
-
renderer: createRenderer(),
|
|
1862
|
-
plugins: [
|
|
1863
|
-
navigationPlugin,
|
|
1864
|
-
createSkykitJourneyPlugin({ journey }),
|
|
1865
|
-
],
|
|
1866
|
-
});
|
|
1867
|
-
|
|
1868
|
-
await flushMicrotasks();
|
|
1869
|
-
viewer.update(0);
|
|
1870
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'local');
|
|
1871
|
-
await flushMicrotasks();
|
|
1872
|
-
|
|
1873
|
-
viewer.update(1);
|
|
1874
|
-
viewer.update(0);
|
|
1875
|
-
assert.ok(viewer.getViewState().observerPc.z > 20);
|
|
1876
|
-
|
|
1877
|
-
viewer.update(1.1);
|
|
1878
|
-
await flushMicrotasks();
|
|
1879
|
-
viewer.update(0.1);
|
|
1880
|
-
viewer.update(0);
|
|
1881
|
-
assert.deepEqual(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 });
|
|
1882
|
-
|
|
1883
|
-
await viewer.dispose();
|
|
1884
|
-
});
|
|
1885
|
-
|
|
1886
|
-
test('journey plugin emits timed preload hints once and not on every frame', async () => {
|
|
1887
|
-
const preloadEvents = [];
|
|
1888
|
-
const viewer = await createSkykitViewer({
|
|
1889
|
-
renderer: createRenderer(),
|
|
1890
|
-
plugins: [
|
|
1891
|
-
createSkykitJourneyPlugin({
|
|
1892
|
-
autoPlay: true,
|
|
1893
|
-
timedJourney: {
|
|
1894
|
-
durationSecs: 4,
|
|
1895
|
-
locationWaypoints: [
|
|
1896
|
-
{ id: 'a', timeSecs: 0, positionPc: { x: 0, y: 0, z: 0 } },
|
|
1897
|
-
{ id: 'b', timeSecs: 4, positionPc: { x: 4, y: 0, z: 0 } },
|
|
1898
|
-
],
|
|
1899
|
-
},
|
|
1900
|
-
evaluatorOptions: {
|
|
1901
|
-
pathRadiusPc: 2,
|
|
1902
|
-
lookaheadSecs: 1,
|
|
1903
|
-
preloadStepSecs: 1,
|
|
1904
|
-
},
|
|
1905
|
-
onPreloadHints: (hints, source) => preloadEvents.push({ hints, source }),
|
|
1906
|
-
}),
|
|
1907
|
-
],
|
|
1591
|
+
await viewer.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
|
|
1592
|
+
lookAt: '05:36:12.81, −01:12:06.9',
|
|
1593
|
+
orientation: { durationSecs: 1 },
|
|
1908
1594
|
});
|
|
1909
|
-
|
|
1910
|
-
assert.equal(preloadEvents.length, 1);
|
|
1911
|
-
assert.equal(preloadEvents[0].source.type, 'journey/timed-preload');
|
|
1912
1595
|
viewer.update(1);
|
|
1913
|
-
viewer.update(1);
|
|
1914
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.seek, { timeSecs: 2 });
|
|
1915
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.play);
|
|
1916
|
-
assert.equal(preloadEvents.length, 1);
|
|
1917
|
-
|
|
1918
|
-
await viewer.dispose();
|
|
1919
|
-
});
|
|
1920
|
-
|
|
1921
|
-
test('journey plugin reports scene arrival immediately for static scenes', async () => {
|
|
1922
|
-
const arrivals = [];
|
|
1923
|
-
const viewer = await createSkykitViewer({
|
|
1924
|
-
renderer: createRenderer(),
|
|
1925
|
-
plugins: [
|
|
1926
|
-
createSkykitJourneyPlugin({
|
|
1927
|
-
scenes: {
|
|
1928
|
-
intro: { view: { limitingMagnitude: 5 } },
|
|
1929
|
-
},
|
|
1930
|
-
initialSceneId: 'intro',
|
|
1931
|
-
onSceneArrive(scene) {
|
|
1932
|
-
arrivals.push(scene.sceneId);
|
|
1933
|
-
},
|
|
1934
|
-
}),
|
|
1935
|
-
],
|
|
1936
|
-
});
|
|
1937
|
-
|
|
1938
|
-
await flushMicrotasks();
|
|
1939
|
-
viewer.update(0);
|
|
1940
|
-
assert.deepEqual(arrivals, ['intro']);
|
|
1941
|
-
|
|
1942
|
-
await viewer.dispose();
|
|
1943
|
-
});
|
|
1944
|
-
|
|
1945
|
-
test('journey plugin reports scene arrival after transitionTo completes', async () => {
|
|
1946
|
-
const arrivals = [];
|
|
1947
|
-
const viewer = await createSkykitViewer({
|
|
1948
|
-
renderer: createRenderer(),
|
|
1949
|
-
plugins: [
|
|
1950
|
-
createSkykitNavigationPlugin(),
|
|
1951
|
-
createSkykitJourneyPlugin({
|
|
1952
|
-
scenes: {
|
|
1953
|
-
intro: {},
|
|
1954
|
-
away: {
|
|
1955
|
-
navigation: {
|
|
1956
|
-
transitionTo: {
|
|
1957
|
-
observerPc: { x: 10, y: 0, z: 0 },
|
|
1958
|
-
durationSecs: 1,
|
|
1959
|
-
},
|
|
1960
|
-
},
|
|
1961
|
-
},
|
|
1962
|
-
},
|
|
1963
|
-
initialSceneId: 'intro',
|
|
1964
|
-
onSceneArrive(scene) {
|
|
1965
|
-
arrivals.push(scene.sceneId);
|
|
1966
|
-
},
|
|
1967
|
-
}),
|
|
1968
|
-
],
|
|
1969
|
-
});
|
|
1970
|
-
|
|
1971
|
-
await flushMicrotasks();
|
|
1972
|
-
arrivals.length = 0;
|
|
1973
|
-
|
|
1974
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'away');
|
|
1975
|
-
await flushMicrotasks();
|
|
1976
|
-
viewer.update(0.5);
|
|
1977
|
-
viewer.update(0);
|
|
1978
|
-
assert.deepEqual(arrivals, []);
|
|
1979
|
-
|
|
1980
|
-
viewer.update(0.5);
|
|
1981
1596
|
viewer.update(0);
|
|
1982
|
-
|
|
1597
|
+
assertVectorApprox(
|
|
1598
|
+
localVectorFromView(viewer.getViewState(), { x: 0, y: 0, z: -1 }),
|
|
1599
|
+
directionFromRaDec(84.053375, -1.2019166666666667),
|
|
1600
|
+
);
|
|
1983
1601
|
|
|
1984
1602
|
await viewer.dispose();
|
|
1985
1603
|
});
|
|
1986
1604
|
|
|
1987
|
-
test('journey plugin reports scene arrival after orbit-transfer travel completes', async () => {
|
|
1988
|
-
const arrivals = [];
|
|
1989
|
-
const journey = createJourney({
|
|
1990
|
-
initial: 'sun',
|
|
1991
|
-
order: ['sun', 'cluster'],
|
|
1992
|
-
targets: {
|
|
1993
|
-
sun: { positionPc: { x: 0, y: 0, z: 0 } },
|
|
1994
|
-
cluster: { positionPc: { x: 20, y: 0, z: 0 } },
|
|
1995
|
-
},
|
|
1996
|
-
scenes: {
|
|
1997
|
-
sun: {
|
|
1998
|
-
camera: {
|
|
1999
|
-
type: 'orbit',
|
|
2000
|
-
center: 'sun',
|
|
2001
|
-
radiusPc: 4,
|
|
2002
|
-
angularSpeedRadPerSec: 0.2,
|
|
2003
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
2004
|
-
},
|
|
2005
|
-
},
|
|
2006
|
-
cluster: {
|
|
2007
|
-
camera: {
|
|
2008
|
-
type: 'orbit',
|
|
2009
|
-
center: 'cluster',
|
|
2010
|
-
radiusPc: 5,
|
|
2011
|
-
angularSpeedRadPerSec: 0.2,
|
|
2012
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
2013
|
-
},
|
|
2014
|
-
},
|
|
2015
|
-
},
|
|
2016
|
-
travel: { type: 'orbit-transfer', durationSecs: 1, sampleStepSecs: 0.25 },
|
|
2017
|
-
});
|
|
2018
|
-
const viewer = await createSkykitViewer({
|
|
2019
|
-
renderer: createRenderer(),
|
|
2020
|
-
plugins: [
|
|
2021
|
-
createSkykitNavigationPlugin(),
|
|
2022
|
-
createSkykitJourneyPlugin({
|
|
2023
|
-
journey,
|
|
2024
|
-
onSceneArrive(scene) {
|
|
2025
|
-
arrivals.push(scene.sceneId);
|
|
2026
|
-
},
|
|
2027
|
-
}),
|
|
2028
|
-
],
|
|
2029
|
-
});
|
|
2030
|
-
|
|
2031
|
-
await flushMicrotasks();
|
|
2032
|
-
arrivals.length = 0;
|
|
2033
|
-
|
|
2034
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'cluster');
|
|
2035
|
-
await flushMicrotasks();
|
|
2036
|
-
viewer.update(0.5);
|
|
2037
|
-
viewer.update(0);
|
|
2038
|
-
assert.deepEqual(arrivals, []);
|
|
2039
|
-
|
|
2040
|
-
viewer.update(0.6);
|
|
2041
|
-
viewer.update(0);
|
|
2042
|
-
assert.deepEqual(arrivals, ['cluster']);
|
|
2043
|
-
|
|
2044
|
-
await viewer.dispose();
|
|
2045
|
-
});
|
|
2046
1605
|
|
|
2047
1606
|
test('spatial preload hints map to star-octree requests without exposing provider internals', () => {
|
|
2048
1607
|
const hints = [
|
|
@@ -2460,6 +2019,34 @@ function assertVectorApprox(actual, expected, epsilon = 1e-9) {
|
|
|
2460
2019
|
assert.ok(Math.abs(actual.z - expected.z) < epsilon, `z ${actual.z} !== ${expected.z}`);
|
|
2461
2020
|
}
|
|
2462
2021
|
|
|
2022
|
+
function subtractVectors(a, b) {
|
|
2023
|
+
return {
|
|
2024
|
+
x: a.x - b.x,
|
|
2025
|
+
y: a.y - b.y,
|
|
2026
|
+
z: a.z - b.z,
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
function normalizeVector(vector) {
|
|
2031
|
+
const length = Math.hypot(vector.x, vector.y, vector.z);
|
|
2032
|
+
return {
|
|
2033
|
+
x: vector.x / length,
|
|
2034
|
+
y: vector.y / length,
|
|
2035
|
+
z: vector.z / length,
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
function directionFromRaDec(raDeg, decDeg) {
|
|
2040
|
+
const ra = raDeg * Math.PI / 180;
|
|
2041
|
+
const dec = decDeg * Math.PI / 180;
|
|
2042
|
+
const cosDec = Math.cos(dec);
|
|
2043
|
+
return {
|
|
2044
|
+
x: Math.cos(ra) * cosDec,
|
|
2045
|
+
y: Math.sin(ra) * cosDec,
|
|
2046
|
+
z: Math.sin(dec),
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2463
2050
|
function createPointerTarget() {
|
|
2464
2051
|
const target = createEventTarget();
|
|
2465
2052
|
target.clientWidth = 800;
|
package/src/actions.js
CHANGED
|
@@ -51,14 +51,6 @@ export const SKYKIT_ACTIONS = Object.freeze({
|
|
|
51
51
|
exit: 'skykit:xr.exit',
|
|
52
52
|
toggle: 'skykit:xr.toggle',
|
|
53
53
|
}),
|
|
54
|
-
journey: Object.freeze({
|
|
55
|
-
goToChapter: 'skykit:journey.goToChapter',
|
|
56
|
-
next: 'skykit:journey.next',
|
|
57
|
-
previous: 'skykit:journey.previous',
|
|
58
|
-
seek: 'skykit:journey.seek',
|
|
59
|
-
play: 'skykit:journey.play',
|
|
60
|
-
pause: 'skykit:journey.pause',
|
|
61
|
-
}),
|
|
62
54
|
});
|
|
63
55
|
|
|
64
56
|
export const SKYKIT_CONTROLS = Object.freeze({
|