@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
|
@@ -1,7 +1,6 @@
|
|
|
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,
|
|
@@ -23,7 +22,6 @@ import {
|
|
|
23
22
|
createSkykitDefaultKeyboardNavigationBindings,
|
|
24
23
|
createSkykitAnimationLoop,
|
|
25
24
|
createSkykitDebugBridge,
|
|
26
|
-
createSkykitJourneyPlugin,
|
|
27
25
|
createSkykitNavigationPlugin,
|
|
28
26
|
createSkykitStarPreloadRequestsFromSpatialHints,
|
|
29
27
|
createSkykitStarStrategiesFromSpatialHints,
|
|
@@ -77,6 +75,57 @@ function createRenderer() {
|
|
|
77
75
|
};
|
|
78
76
|
}
|
|
79
77
|
|
|
78
|
+
function createTextureRenderer() {
|
|
79
|
+
const renderer = createRenderer();
|
|
80
|
+
return {
|
|
81
|
+
...renderer,
|
|
82
|
+
xr: { enabled: true },
|
|
83
|
+
currentTarget: null,
|
|
84
|
+
viewport: new THREE.Vector4(0, 0, 1, 1),
|
|
85
|
+
scissor: new THREE.Vector4(0, 0, 1, 1),
|
|
86
|
+
scissorTest: false,
|
|
87
|
+
getRenderTarget() {
|
|
88
|
+
return this.currentTarget;
|
|
89
|
+
},
|
|
90
|
+
setRenderTarget(target) {
|
|
91
|
+
this.currentTarget = target;
|
|
92
|
+
},
|
|
93
|
+
getViewport(target) {
|
|
94
|
+
return target.copy(this.viewport);
|
|
95
|
+
},
|
|
96
|
+
setViewport(value) {
|
|
97
|
+
if (value?.isVector4) this.viewport.copy(value);
|
|
98
|
+
},
|
|
99
|
+
getScissor(target) {
|
|
100
|
+
return target.copy(this.scissor);
|
|
101
|
+
},
|
|
102
|
+
setScissor(value) {
|
|
103
|
+
if (value?.isVector4) this.scissor.copy(value);
|
|
104
|
+
},
|
|
105
|
+
getScissorTest() {
|
|
106
|
+
return this.scissorTest;
|
|
107
|
+
},
|
|
108
|
+
setScissorTest(value) {
|
|
109
|
+
this.scissorTest = Boolean(value);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createEmbeddedSurfaceSpy() {
|
|
115
|
+
const publishCalls = [];
|
|
116
|
+
const unpublishCalls = [];
|
|
117
|
+
return {
|
|
118
|
+
publishCalls,
|
|
119
|
+
unpublishCalls,
|
|
120
|
+
publish(sourceId, update) {
|
|
121
|
+
publishCalls.push({ sourceId, update });
|
|
122
|
+
},
|
|
123
|
+
unpublish(sourceId) {
|
|
124
|
+
unpublishCalls.push(sourceId);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
80
129
|
async function flushMicrotasks(count = 10) {
|
|
81
130
|
for (let index = 0; index < count; index += 1) {
|
|
82
131
|
await Promise.resolve();
|
|
@@ -92,10 +141,6 @@ function assertStrategyBehavior(strategy) {
|
|
|
92
141
|
}
|
|
93
142
|
}
|
|
94
143
|
|
|
95
|
-
function distance(a, b) {
|
|
96
|
-
return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
144
|
test('createSkykitViewer creates roots, mounts renderer, runs lifecycle, and disposes cleanly', async () => {
|
|
100
145
|
const host = createHost();
|
|
101
146
|
const renderer = createRenderer();
|
|
@@ -242,6 +287,51 @@ test('viewer exposes action registry, emits action events, and resets to initial
|
|
|
242
287
|
await viewer.dispose();
|
|
243
288
|
});
|
|
244
289
|
|
|
290
|
+
test('viewer derives camera orientation from lookAt targets, sky coordinates, and stars', async () => {
|
|
291
|
+
const targetViewer = await createSkykitViewer({
|
|
292
|
+
renderer: createRenderer(),
|
|
293
|
+
view: {
|
|
294
|
+
observerPc: { x: 0, y: 0, z: 0 },
|
|
295
|
+
lookAt: { targetPc: { x: 10, y: 0, z: 0 }, positionAngleDeg: 0 },
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
let view = targetViewer.getViewState();
|
|
299
|
+
assert.deepEqual(view.targetPc, { x: 10, y: 0, z: 0 });
|
|
300
|
+
assertVectorApprox(localVectorFromView(view, { x: 0, y: 0, z: -1 }), { x: 1, y: 0, z: 0 });
|
|
301
|
+
assertVectorApprox(localVectorFromView(view, { x: 0, y: 1, z: 0 }), { x: 0, y: 0, z: 1 });
|
|
302
|
+
await targetViewer.dispose();
|
|
303
|
+
|
|
304
|
+
const skyViewer = await createSkykitViewer({
|
|
305
|
+
renderer: createRenderer(),
|
|
306
|
+
view: {
|
|
307
|
+
lookAt: { raDeg: 0, decDeg: 0, positionAngleDeg: 90 },
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
view = skyViewer.getViewState();
|
|
311
|
+
assert.equal(view.targetPc, null);
|
|
312
|
+
assertVectorApprox(localVectorFromView(view, { x: 0, y: 0, z: -1 }), { x: 1, y: 0, z: 0 });
|
|
313
|
+
assertVectorApprox(localVectorFromView(view, { x: 0, y: 1, z: 0 }), { x: 0, y: 1, z: 0 });
|
|
314
|
+
await skyViewer.dispose();
|
|
315
|
+
|
|
316
|
+
const starViewer = await createSkykitViewer({
|
|
317
|
+
renderer: createRenderer(),
|
|
318
|
+
view: { lookAt: { star: 'hyades' } },
|
|
319
|
+
resolveLookAtStar: async (star) => star === 'hyades'
|
|
320
|
+
? { targetPc: { x: 4, y: 5, z: 6 } }
|
|
321
|
+
: null,
|
|
322
|
+
});
|
|
323
|
+
view = starViewer.getViewState();
|
|
324
|
+
assert.equal(view.lookAt?.star, 'hyades');
|
|
325
|
+
assert.deepEqual(view.targetPc, { x: 4, y: 5, z: 6 });
|
|
326
|
+
assert.ok(view.orientationIcrs);
|
|
327
|
+
|
|
328
|
+
starViewer.requestViewState({ lookAt: { star: 'orion' } }, 'test-star-look');
|
|
329
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
330
|
+
starViewer.update(0);
|
|
331
|
+
assert.deepEqual(starViewer.getViewState().targetPc, null);
|
|
332
|
+
await starViewer.dispose();
|
|
333
|
+
});
|
|
334
|
+
|
|
245
335
|
test('requestViewState batches patches and observer-centric root follows translation without rotation', async () => {
|
|
246
336
|
const viewer = await createSkykitViewer({
|
|
247
337
|
renderer: createRenderer(),
|
|
@@ -333,15 +423,15 @@ test('action registry registers contexts, invokes multiple handlers, and reports
|
|
|
333
423
|
offLow();
|
|
334
424
|
assert.equal(registry.listActions().find((entry) => entry.id === 'lesson:demo.run')?.handlerCount, 2);
|
|
335
425
|
|
|
336
|
-
const
|
|
337
|
-
|
|
426
|
+
const offChapters = registry.registerContext('lesson:chapters', {
|
|
427
|
+
goTo({ payload }) {
|
|
338
428
|
calls.push(`chapter:${payload}`);
|
|
339
429
|
},
|
|
340
430
|
});
|
|
341
|
-
await registry.invoke(
|
|
431
|
+
await registry.invoke('lesson:chapters.goTo', 'intro');
|
|
342
432
|
assert.equal(calls.at(-1), 'chapter:intro');
|
|
343
|
-
|
|
344
|
-
assert.equal(registry.listActions().some((entry) => entry.id ===
|
|
433
|
+
offChapters();
|
|
434
|
+
assert.equal(registry.listActions().some((entry) => entry.id === 'lesson:chapters.goTo'), false);
|
|
345
435
|
});
|
|
346
436
|
|
|
347
437
|
test('action registry tracks held action sources and control values', () => {
|
|
@@ -682,6 +772,41 @@ test('HR diagram mode changes refresh shared demand and update the renderer view
|
|
|
682
772
|
await viewer.dispose();
|
|
683
773
|
});
|
|
684
774
|
|
|
775
|
+
test('HR diagram touch-os surfaces can be resolved lazily from panel runtimes', async () => {
|
|
776
|
+
const session = createFakeSession();
|
|
777
|
+
const provider = {
|
|
778
|
+
id: 'provider',
|
|
779
|
+
createSession() {
|
|
780
|
+
return session;
|
|
781
|
+
},
|
|
782
|
+
};
|
|
783
|
+
const source = createSkykitStarSourcePlugin({ provider });
|
|
784
|
+
const surfaces = createEmbeddedSurfaceSpy();
|
|
785
|
+
let activeSurfaces = null;
|
|
786
|
+
const hr = createSkykitHrDiagramPlugin({
|
|
787
|
+
id: 'hr',
|
|
788
|
+
source,
|
|
789
|
+
touchOs: {
|
|
790
|
+
sourceId: 'hr:surface',
|
|
791
|
+
surfaces: () => activeSurfaces,
|
|
792
|
+
},
|
|
793
|
+
});
|
|
794
|
+
const viewer = await createSkykitViewer({
|
|
795
|
+
renderer: createTextureRenderer(),
|
|
796
|
+
plugins: [source, hr],
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
activeSurfaces = surfaces;
|
|
800
|
+
viewer.frame(0.016);
|
|
801
|
+
|
|
802
|
+
assert.equal(surfaces.publishCalls.length, 1);
|
|
803
|
+
assert.equal(surfaces.publishCalls[0].sourceId, 'hr:surface');
|
|
804
|
+
assert.equal(surfaces.publishCalls[0].update.available, true);
|
|
805
|
+
|
|
806
|
+
await viewer.dispose();
|
|
807
|
+
assert.deepEqual(surfaces.unpublishCalls, ['hr:surface']);
|
|
808
|
+
});
|
|
809
|
+
|
|
685
810
|
test('HR diagram demand strategy override can be supplied and restored at runtime', async () => {
|
|
686
811
|
const sessions = [];
|
|
687
812
|
const provider = {
|
|
@@ -1417,501 +1542,6 @@ test('navigation transition action restores pose with independent lane durations
|
|
|
1417
1542
|
await viewer.dispose();
|
|
1418
1543
|
});
|
|
1419
1544
|
|
|
1420
|
-
test('journey plugin registers actions, applies scenes, timed frames, and preload hooks', async () => {
|
|
1421
|
-
const preloadEvents = [];
|
|
1422
|
-
const cueEvents = [];
|
|
1423
|
-
const journeyPlugin = createSkykitJourneyPlugin({
|
|
1424
|
-
scenes: {
|
|
1425
|
-
intro: { title: 'Intro', view: { limitingMagnitude: 5 } },
|
|
1426
|
-
hyades: {
|
|
1427
|
-
title: 'Hyades',
|
|
1428
|
-
view: { observerPc: { x: 1, y: 2, z: 3 } },
|
|
1429
|
-
preloadHints: [
|
|
1430
|
-
{ kind: 'sphere-volume', centerPc: { x: 1, y: 2, z: 3 }, radiusPc: 4 },
|
|
1431
|
-
],
|
|
1432
|
-
},
|
|
1433
|
-
},
|
|
1434
|
-
initialSceneId: 'intro',
|
|
1435
|
-
timedJourney: {
|
|
1436
|
-
durationSecs: 2,
|
|
1437
|
-
locationWaypoints: [
|
|
1438
|
-
{ id: 'a', timeSecs: 0, positionPc: { x: 0, y: 0, z: 0 } },
|
|
1439
|
-
{ id: 'b', timeSecs: 2, positionPc: { x: 2, y: 0, z: 0 } },
|
|
1440
|
-
],
|
|
1441
|
-
cameraLookWaypoints: [
|
|
1442
|
-
{ id: 'look', timeSecs: 0, kind: 'direction', forward: { x: 1, y: 0, z: 0 } },
|
|
1443
|
-
],
|
|
1444
|
-
cues: [{ id: 'cue', startSecs: 0, endSecs: 2 }],
|
|
1445
|
-
},
|
|
1446
|
-
onPreloadHints: (hints) => preloadEvents.push(hints),
|
|
1447
|
-
onCue: (cue) => cueEvents.push(cue.id),
|
|
1448
|
-
});
|
|
1449
|
-
const viewer = await createSkykitViewer({
|
|
1450
|
-
renderer: createRenderer(),
|
|
1451
|
-
plugins: [journeyPlugin],
|
|
1452
|
-
});
|
|
1453
|
-
|
|
1454
|
-
viewer.update(0);
|
|
1455
|
-
assert.equal(viewer.getViewState().limitingMagnitude, 5);
|
|
1456
|
-
assert.equal(journeyPlugin.getSnapshot().initialSceneApplied, true);
|
|
1457
|
-
assert.equal(journeyPlugin.getSnapshot().timedPreloadHintsEmitted, true);
|
|
1458
|
-
|
|
1459
|
-
assert.equal(viewer.actions.listActions().some((entry) => entry.id === SKYKIT_ACTIONS.journey.goToChapter), true);
|
|
1460
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'hyades');
|
|
1461
|
-
viewer.update(0);
|
|
1462
|
-
assert.deepEqual(viewer.getViewState().observerPc, { x: 1, y: 2, z: 3 });
|
|
1463
|
-
assert.equal(preloadEvents.length, 1);
|
|
1464
|
-
|
|
1465
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.seek, { timeSecs: 1 });
|
|
1466
|
-
viewer.update(0);
|
|
1467
|
-
assert.ok(viewer.getViewState().observerPc.x > 0);
|
|
1468
|
-
assert.deepEqual(cueEvents, ['cue']);
|
|
1469
|
-
|
|
1470
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.play);
|
|
1471
|
-
const beforePlayX = viewer.getViewState().observerPc.x;
|
|
1472
|
-
viewer.update(0.5);
|
|
1473
|
-
viewer.update(0);
|
|
1474
|
-
assert.ok(viewer.getViewState().observerPc.x > beforePlayX);
|
|
1475
|
-
|
|
1476
|
-
await viewer.dispose();
|
|
1477
|
-
});
|
|
1478
|
-
|
|
1479
|
-
test('journey plugin executes semantic orbit-transfer scenes without snapping', async () => {
|
|
1480
|
-
const navigationPlugin = createSkykitNavigationPlugin({ speed: 20, acceleration: 20, deceleration: 20 });
|
|
1481
|
-
const journey = createJourney({
|
|
1482
|
-
initial: 'inside',
|
|
1483
|
-
order: ['inside', 'outside', 'hyades', 'free'],
|
|
1484
|
-
targets: {
|
|
1485
|
-
sun: { positionPc: { x: 0, y: 0, z: 0 } },
|
|
1486
|
-
hyades: { positionPc: { x: 18, y: 42, z: 7 } },
|
|
1487
|
-
free: { positionPc: { x: -20, y: 35, z: 12 } },
|
|
1488
|
-
orion: { positionPc: { x: 0, y: 0, z: -100 } },
|
|
1489
|
-
},
|
|
1490
|
-
scenes: {
|
|
1491
|
-
inside: {
|
|
1492
|
-
view: {
|
|
1493
|
-
observerPc: { x: 0, y: 4, z: 0 },
|
|
1494
|
-
},
|
|
1495
|
-
camera: {
|
|
1496
|
-
type: 'orbit',
|
|
1497
|
-
center: 'sun',
|
|
1498
|
-
radiusPc: 4,
|
|
1499
|
-
angularSpeedRadPerSec: 0.4,
|
|
1500
|
-
lookAt: 'orion',
|
|
1501
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1502
|
-
},
|
|
1503
|
-
},
|
|
1504
|
-
outside: {
|
|
1505
|
-
camera: {
|
|
1506
|
-
type: 'orbit',
|
|
1507
|
-
center: 'sun',
|
|
1508
|
-
radiusPc: 10,
|
|
1509
|
-
angularSpeedRadPerSec: 0.2,
|
|
1510
|
-
lookAt: 'sun',
|
|
1511
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1512
|
-
},
|
|
1513
|
-
},
|
|
1514
|
-
hyades: {
|
|
1515
|
-
camera: {
|
|
1516
|
-
type: 'orbit',
|
|
1517
|
-
center: 'hyades',
|
|
1518
|
-
radiusPc: 6,
|
|
1519
|
-
angularSpeedRadPerSec: 0.3,
|
|
1520
|
-
lookAt: 'hyades',
|
|
1521
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1522
|
-
},
|
|
1523
|
-
},
|
|
1524
|
-
free: {
|
|
1525
|
-
camera: {
|
|
1526
|
-
type: 'orbit',
|
|
1527
|
-
center: 'free',
|
|
1528
|
-
radiusPc: 5,
|
|
1529
|
-
angularSpeedRadPerSec: 0.25,
|
|
1530
|
-
lookAt: 'free',
|
|
1531
|
-
},
|
|
1532
|
-
},
|
|
1533
|
-
},
|
|
1534
|
-
travel: { type: 'orbit-transfer', durationSecs: 2, sampleStepSecs: 0.25 },
|
|
1535
|
-
});
|
|
1536
|
-
const viewer = await createSkykitViewer({
|
|
1537
|
-
renderer: createRenderer(),
|
|
1538
|
-
plugins: [
|
|
1539
|
-
navigationPlugin,
|
|
1540
|
-
createSkykitJourneyPlugin({ journey }),
|
|
1541
|
-
],
|
|
1542
|
-
});
|
|
1543
|
-
|
|
1544
|
-
await flushMicrotasks();
|
|
1545
|
-
viewer.update(0);
|
|
1546
|
-
assert.deepEqual(viewer.getViewState().observerPc, { x: 0, y: 4, z: 0 });
|
|
1547
|
-
viewer.update(0.001);
|
|
1548
|
-
viewer.update(0);
|
|
1549
|
-
assert.ok(Math.abs(distance(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 }) - 4) < 1e-9);
|
|
1550
|
-
|
|
1551
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'outside');
|
|
1552
|
-
await flushMicrotasks();
|
|
1553
|
-
viewer.update(1);
|
|
1554
|
-
viewer.update(0);
|
|
1555
|
-
const halfwayRadius = distance(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 });
|
|
1556
|
-
assert.ok(halfwayRadius > 4);
|
|
1557
|
-
assert.ok(halfwayRadius < 10);
|
|
1558
|
-
|
|
1559
|
-
viewer.update(1.1);
|
|
1560
|
-
viewer.update(0);
|
|
1561
|
-
const arrival = { ...viewer.getViewState().observerPc };
|
|
1562
|
-
assert.ok(Math.abs(distance(arrival, { x: 0, y: 0, z: 0 }) - 10) < 1e-9);
|
|
1563
|
-
assert.equal(navigationPlugin.getSnapshot().navigation.activeAutomation, 'orbit');
|
|
1564
|
-
viewer.update(0.25);
|
|
1565
|
-
viewer.update(0);
|
|
1566
|
-
assert.ok(Math.abs(distance(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 }) - 10) < 1e-9);
|
|
1567
|
-
assert.notDeepEqual(viewer.getViewState().observerPc, arrival);
|
|
1568
|
-
|
|
1569
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'hyades');
|
|
1570
|
-
await flushMicrotasks();
|
|
1571
|
-
let handoff = null;
|
|
1572
|
-
let previous = { ...viewer.getViewState().observerPc };
|
|
1573
|
-
for (let index = 0; index < 120; index += 1) {
|
|
1574
|
-
viewer.update(1 / 30);
|
|
1575
|
-
viewer.update(0);
|
|
1576
|
-
const current = { ...viewer.getViewState().observerPc };
|
|
1577
|
-
const step = distance(previous, current);
|
|
1578
|
-
if (!handoff && navigationPlugin.getSnapshot().navigation.activeAutomation === 'orbit') {
|
|
1579
|
-
handoff = { position: current, step };
|
|
1580
|
-
}
|
|
1581
|
-
previous = current;
|
|
1582
|
-
}
|
|
1583
|
-
assert.ok(handoff);
|
|
1584
|
-
assert.ok(Math.abs(handoff.position.z - 7) < 1e-6);
|
|
1585
|
-
assert.ok(handoff.step < 0.5);
|
|
1586
|
-
|
|
1587
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'free');
|
|
1588
|
-
await flushMicrotasks();
|
|
1589
|
-
for (let index = 0; index < 120; index += 1) {
|
|
1590
|
-
viewer.update(1 / 30);
|
|
1591
|
-
viewer.update(0);
|
|
1592
|
-
}
|
|
1593
|
-
const freeAutomation = navigationPlugin.getSnapshot().navigation.movementAutomation;
|
|
1594
|
-
assert.equal(navigationPlugin.getSnapshot().navigation.activeAutomation, 'orbit');
|
|
1595
|
-
assert.ok(freeAutomation?.normal);
|
|
1596
|
-
assert.ok(Math.abs(freeAutomation.normal.z - 1) > 0.001);
|
|
1597
|
-
assert.ok(Math.abs(distance(viewer.getViewState().observerPc, { x: -20, y: 35, z: 12 }) - 5) < 1e-9);
|
|
1598
|
-
|
|
1599
|
-
await viewer.dispose();
|
|
1600
|
-
});
|
|
1601
|
-
|
|
1602
|
-
test('journey plugin uses explicit route points for authored orbit transfers', async () => {
|
|
1603
|
-
const navigationPlugin = createSkykitNavigationPlugin({ speed: 80, acceleration: 80, deceleration: 80 });
|
|
1604
|
-
const authoredPoints = [
|
|
1605
|
-
{ x: 0, y: 4, z: 0 },
|
|
1606
|
-
{ x: 40, y: 10, z: 80 },
|
|
1607
|
-
{ x: 100, y: 0, z: 10 },
|
|
1608
|
-
];
|
|
1609
|
-
const journey = createJourney({
|
|
1610
|
-
initial: 'inside',
|
|
1611
|
-
order: ['inside', 'omega'],
|
|
1612
|
-
targets: {
|
|
1613
|
-
sun: { positionPc: { x: 0, y: 0, z: 0 } },
|
|
1614
|
-
omega: { positionPc: { x: 100, y: 0, z: 0 } },
|
|
1615
|
-
},
|
|
1616
|
-
scenes: {
|
|
1617
|
-
inside: {
|
|
1618
|
-
view: { observerPc: { x: 0, y: 4, z: 0 } },
|
|
1619
|
-
camera: {
|
|
1620
|
-
type: 'orbit',
|
|
1621
|
-
center: 'sun',
|
|
1622
|
-
radiusPc: 4,
|
|
1623
|
-
angularSpeedRadPerSec: 0.2,
|
|
1624
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1625
|
-
},
|
|
1626
|
-
},
|
|
1627
|
-
omega: {
|
|
1628
|
-
camera: {
|
|
1629
|
-
type: 'orbit',
|
|
1630
|
-
center: 'omega',
|
|
1631
|
-
radiusPc: 10,
|
|
1632
|
-
angularSpeedRadPerSec: 0.12,
|
|
1633
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1634
|
-
},
|
|
1635
|
-
},
|
|
1636
|
-
},
|
|
1637
|
-
transitions: [
|
|
1638
|
-
{
|
|
1639
|
-
fromSceneId: 'inside',
|
|
1640
|
-
toSceneId: 'omega',
|
|
1641
|
-
travel: {
|
|
1642
|
-
type: 'orbit-transfer',
|
|
1643
|
-
durationSecs: 2,
|
|
1644
|
-
pointsPc: authoredPoints,
|
|
1645
|
-
arrivalAction: {
|
|
1646
|
-
type: 'orbit',
|
|
1647
|
-
center: { x: 100, y: 0, z: 0 },
|
|
1648
|
-
radius: 10,
|
|
1649
|
-
angularSpeedRadPerSec: 0.12,
|
|
1650
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1651
|
-
},
|
|
1652
|
-
},
|
|
1653
|
-
},
|
|
1654
|
-
],
|
|
1655
|
-
});
|
|
1656
|
-
const viewer = await createSkykitViewer({
|
|
1657
|
-
renderer: createRenderer(),
|
|
1658
|
-
plugins: [
|
|
1659
|
-
navigationPlugin,
|
|
1660
|
-
createSkykitJourneyPlugin({ journey }),
|
|
1661
|
-
],
|
|
1662
|
-
});
|
|
1663
|
-
|
|
1664
|
-
await flushMicrotasks();
|
|
1665
|
-
viewer.update(0);
|
|
1666
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'omega');
|
|
1667
|
-
await flushMicrotasks();
|
|
1668
|
-
|
|
1669
|
-
viewer.update(1);
|
|
1670
|
-
viewer.update(0);
|
|
1671
|
-
assert.ok(viewer.getViewState().observerPc.z > 20);
|
|
1672
|
-
|
|
1673
|
-
viewer.update(1.1);
|
|
1674
|
-
viewer.update(0);
|
|
1675
|
-
assert.equal(navigationPlugin.getSnapshot().navigation.activeAutomation, 'orbit');
|
|
1676
|
-
assert.ok(Math.abs(distance(viewer.getViewState().observerPc, { x: 100, y: 0, z: 0 }) - 10) < 1e-9);
|
|
1677
|
-
|
|
1678
|
-
await viewer.dispose();
|
|
1679
|
-
});
|
|
1680
|
-
|
|
1681
|
-
test('journey plugin can route to non-orbit scenes before applying the arrival transition', async () => {
|
|
1682
|
-
const navigationPlugin = createSkykitNavigationPlugin({ speed: 80, acceleration: 80, deceleration: 80 });
|
|
1683
|
-
const journey = createJourney({
|
|
1684
|
-
initial: 'omega',
|
|
1685
|
-
order: ['omega', 'local'],
|
|
1686
|
-
targets: {
|
|
1687
|
-
omega: { positionPc: { x: 100, y: 0, z: 0 } },
|
|
1688
|
-
},
|
|
1689
|
-
scenes: {
|
|
1690
|
-
omega: {
|
|
1691
|
-
view: { observerPc: { x: 100, y: 0, z: 10 } },
|
|
1692
|
-
camera: {
|
|
1693
|
-
type: 'orbit',
|
|
1694
|
-
center: 'omega',
|
|
1695
|
-
radiusPc: 10,
|
|
1696
|
-
angularSpeedRadPerSec: 0.12,
|
|
1697
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1698
|
-
},
|
|
1699
|
-
},
|
|
1700
|
-
local: {
|
|
1701
|
-
view: { targetPc: { x: 1, y: 0, z: 0 } },
|
|
1702
|
-
navigation: {
|
|
1703
|
-
transitionTo: {
|
|
1704
|
-
observerPc: { x: 0, y: 0, z: 0 },
|
|
1705
|
-
orientationIcrs: { x: 0, y: 0, z: 0, w: 1 },
|
|
1706
|
-
durationSecs: 1,
|
|
1707
|
-
movement: { durationSecs: 1 },
|
|
1708
|
-
orientationTransition: { durationSecs: 1 },
|
|
1709
|
-
},
|
|
1710
|
-
},
|
|
1711
|
-
},
|
|
1712
|
-
},
|
|
1713
|
-
transitions: [
|
|
1714
|
-
{
|
|
1715
|
-
fromSceneId: 'omega',
|
|
1716
|
-
toSceneId: 'local',
|
|
1717
|
-
travel: {
|
|
1718
|
-
type: 'orbit-transfer',
|
|
1719
|
-
durationSecs: 2,
|
|
1720
|
-
pointsPc: [
|
|
1721
|
-
{ x: 100, y: 0, z: 10 },
|
|
1722
|
-
{ x: 50, y: 0, z: 80 },
|
|
1723
|
-
{ x: 0, y: 0, z: 0 },
|
|
1724
|
-
],
|
|
1725
|
-
},
|
|
1726
|
-
},
|
|
1727
|
-
],
|
|
1728
|
-
});
|
|
1729
|
-
const viewer = await createSkykitViewer({
|
|
1730
|
-
renderer: createRenderer(),
|
|
1731
|
-
plugins: [
|
|
1732
|
-
navigationPlugin,
|
|
1733
|
-
createSkykitJourneyPlugin({ journey }),
|
|
1734
|
-
],
|
|
1735
|
-
});
|
|
1736
|
-
|
|
1737
|
-
await flushMicrotasks();
|
|
1738
|
-
viewer.update(0);
|
|
1739
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'local');
|
|
1740
|
-
await flushMicrotasks();
|
|
1741
|
-
|
|
1742
|
-
viewer.update(1);
|
|
1743
|
-
viewer.update(0);
|
|
1744
|
-
assert.ok(viewer.getViewState().observerPc.z > 20);
|
|
1745
|
-
|
|
1746
|
-
viewer.update(1.1);
|
|
1747
|
-
await flushMicrotasks();
|
|
1748
|
-
viewer.update(0.1);
|
|
1749
|
-
viewer.update(0);
|
|
1750
|
-
assert.deepEqual(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 });
|
|
1751
|
-
|
|
1752
|
-
await viewer.dispose();
|
|
1753
|
-
});
|
|
1754
|
-
|
|
1755
|
-
test('journey plugin emits timed preload hints once and not on every frame', async () => {
|
|
1756
|
-
const preloadEvents = [];
|
|
1757
|
-
const viewer = await createSkykitViewer({
|
|
1758
|
-
renderer: createRenderer(),
|
|
1759
|
-
plugins: [
|
|
1760
|
-
createSkykitJourneyPlugin({
|
|
1761
|
-
autoPlay: true,
|
|
1762
|
-
timedJourney: {
|
|
1763
|
-
durationSecs: 4,
|
|
1764
|
-
locationWaypoints: [
|
|
1765
|
-
{ id: 'a', timeSecs: 0, positionPc: { x: 0, y: 0, z: 0 } },
|
|
1766
|
-
{ id: 'b', timeSecs: 4, positionPc: { x: 4, y: 0, z: 0 } },
|
|
1767
|
-
],
|
|
1768
|
-
},
|
|
1769
|
-
evaluatorOptions: {
|
|
1770
|
-
pathRadiusPc: 2,
|
|
1771
|
-
lookaheadSecs: 1,
|
|
1772
|
-
preloadStepSecs: 1,
|
|
1773
|
-
},
|
|
1774
|
-
onPreloadHints: (hints, source) => preloadEvents.push({ hints, source }),
|
|
1775
|
-
}),
|
|
1776
|
-
],
|
|
1777
|
-
});
|
|
1778
|
-
|
|
1779
|
-
assert.equal(preloadEvents.length, 1);
|
|
1780
|
-
assert.equal(preloadEvents[0].source.type, 'journey/timed-preload');
|
|
1781
|
-
viewer.update(1);
|
|
1782
|
-
viewer.update(1);
|
|
1783
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.seek, { timeSecs: 2 });
|
|
1784
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.play);
|
|
1785
|
-
assert.equal(preloadEvents.length, 1);
|
|
1786
|
-
|
|
1787
|
-
await viewer.dispose();
|
|
1788
|
-
});
|
|
1789
|
-
|
|
1790
|
-
test('journey plugin reports scene arrival immediately for static scenes', async () => {
|
|
1791
|
-
const arrivals = [];
|
|
1792
|
-
const viewer = await createSkykitViewer({
|
|
1793
|
-
renderer: createRenderer(),
|
|
1794
|
-
plugins: [
|
|
1795
|
-
createSkykitJourneyPlugin({
|
|
1796
|
-
scenes: {
|
|
1797
|
-
intro: { view: { limitingMagnitude: 5 } },
|
|
1798
|
-
},
|
|
1799
|
-
initialSceneId: 'intro',
|
|
1800
|
-
onSceneArrive(scene) {
|
|
1801
|
-
arrivals.push(scene.sceneId);
|
|
1802
|
-
},
|
|
1803
|
-
}),
|
|
1804
|
-
],
|
|
1805
|
-
});
|
|
1806
|
-
|
|
1807
|
-
await flushMicrotasks();
|
|
1808
|
-
viewer.update(0);
|
|
1809
|
-
assert.deepEqual(arrivals, ['intro']);
|
|
1810
|
-
|
|
1811
|
-
await viewer.dispose();
|
|
1812
|
-
});
|
|
1813
|
-
|
|
1814
|
-
test('journey plugin reports scene arrival after transitionTo completes', async () => {
|
|
1815
|
-
const arrivals = [];
|
|
1816
|
-
const viewer = await createSkykitViewer({
|
|
1817
|
-
renderer: createRenderer(),
|
|
1818
|
-
plugins: [
|
|
1819
|
-
createSkykitNavigationPlugin(),
|
|
1820
|
-
createSkykitJourneyPlugin({
|
|
1821
|
-
scenes: {
|
|
1822
|
-
intro: {},
|
|
1823
|
-
away: {
|
|
1824
|
-
navigation: {
|
|
1825
|
-
transitionTo: {
|
|
1826
|
-
observerPc: { x: 10, y: 0, z: 0 },
|
|
1827
|
-
durationSecs: 1,
|
|
1828
|
-
},
|
|
1829
|
-
},
|
|
1830
|
-
},
|
|
1831
|
-
},
|
|
1832
|
-
initialSceneId: 'intro',
|
|
1833
|
-
onSceneArrive(scene) {
|
|
1834
|
-
arrivals.push(scene.sceneId);
|
|
1835
|
-
},
|
|
1836
|
-
}),
|
|
1837
|
-
],
|
|
1838
|
-
});
|
|
1839
|
-
|
|
1840
|
-
await flushMicrotasks();
|
|
1841
|
-
arrivals.length = 0;
|
|
1842
|
-
|
|
1843
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'away');
|
|
1844
|
-
await flushMicrotasks();
|
|
1845
|
-
viewer.update(0.5);
|
|
1846
|
-
viewer.update(0);
|
|
1847
|
-
assert.deepEqual(arrivals, []);
|
|
1848
|
-
|
|
1849
|
-
viewer.update(0.5);
|
|
1850
|
-
viewer.update(0);
|
|
1851
|
-
assert.deepEqual(arrivals, ['away']);
|
|
1852
|
-
|
|
1853
|
-
await viewer.dispose();
|
|
1854
|
-
});
|
|
1855
|
-
|
|
1856
|
-
test('journey plugin reports scene arrival after orbit-transfer travel completes', async () => {
|
|
1857
|
-
const arrivals = [];
|
|
1858
|
-
const journey = createJourney({
|
|
1859
|
-
initial: 'sun',
|
|
1860
|
-
order: ['sun', 'cluster'],
|
|
1861
|
-
targets: {
|
|
1862
|
-
sun: { positionPc: { x: 0, y: 0, z: 0 } },
|
|
1863
|
-
cluster: { positionPc: { x: 20, y: 0, z: 0 } },
|
|
1864
|
-
},
|
|
1865
|
-
scenes: {
|
|
1866
|
-
sun: {
|
|
1867
|
-
camera: {
|
|
1868
|
-
type: 'orbit',
|
|
1869
|
-
center: 'sun',
|
|
1870
|
-
radiusPc: 4,
|
|
1871
|
-
angularSpeedRadPerSec: 0.2,
|
|
1872
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1873
|
-
},
|
|
1874
|
-
},
|
|
1875
|
-
cluster: {
|
|
1876
|
-
camera: {
|
|
1877
|
-
type: 'orbit',
|
|
1878
|
-
center: 'cluster',
|
|
1879
|
-
radiusPc: 5,
|
|
1880
|
-
angularSpeedRadPerSec: 0.2,
|
|
1881
|
-
normal: { x: 0, y: 0, z: 1 },
|
|
1882
|
-
},
|
|
1883
|
-
},
|
|
1884
|
-
},
|
|
1885
|
-
travel: { type: 'orbit-transfer', durationSecs: 1, sampleStepSecs: 0.25 },
|
|
1886
|
-
});
|
|
1887
|
-
const viewer = await createSkykitViewer({
|
|
1888
|
-
renderer: createRenderer(),
|
|
1889
|
-
plugins: [
|
|
1890
|
-
createSkykitNavigationPlugin(),
|
|
1891
|
-
createSkykitJourneyPlugin({
|
|
1892
|
-
journey,
|
|
1893
|
-
onSceneArrive(scene) {
|
|
1894
|
-
arrivals.push(scene.sceneId);
|
|
1895
|
-
},
|
|
1896
|
-
}),
|
|
1897
|
-
],
|
|
1898
|
-
});
|
|
1899
|
-
|
|
1900
|
-
await flushMicrotasks();
|
|
1901
|
-
arrivals.length = 0;
|
|
1902
|
-
|
|
1903
|
-
await viewer.actions.invoke(SKYKIT_ACTIONS.journey.goToChapter, 'cluster');
|
|
1904
|
-
await flushMicrotasks();
|
|
1905
|
-
viewer.update(0.5);
|
|
1906
|
-
viewer.update(0);
|
|
1907
|
-
assert.deepEqual(arrivals, []);
|
|
1908
|
-
|
|
1909
|
-
viewer.update(0.6);
|
|
1910
|
-
viewer.update(0);
|
|
1911
|
-
assert.deepEqual(arrivals, ['cluster']);
|
|
1912
|
-
|
|
1913
|
-
await viewer.dispose();
|
|
1914
|
-
});
|
|
1915
1545
|
|
|
1916
1546
|
test('spatial preload hints map to star-octree requests without exposing provider internals', () => {
|
|
1917
1547
|
const hints = [
|
|
@@ -2323,6 +1953,12 @@ function localVectorFromView(view, vector) {
|
|
|
2323
1953
|
return { x: result.x, y: result.y, z: result.z };
|
|
2324
1954
|
}
|
|
2325
1955
|
|
|
1956
|
+
function assertVectorApprox(actual, expected, epsilon = 1e-9) {
|
|
1957
|
+
assert.ok(Math.abs(actual.x - expected.x) < epsilon, `x ${actual.x} !== ${expected.x}`);
|
|
1958
|
+
assert.ok(Math.abs(actual.y - expected.y) < epsilon, `y ${actual.y} !== ${expected.y}`);
|
|
1959
|
+
assert.ok(Math.abs(actual.z - expected.z) < epsilon, `z ${actual.z} !== ${expected.z}`);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
2326
1962
|
function createPointerTarget() {
|
|
2327
1963
|
const target = createEventTarget();
|
|
2328
1964
|
target.clientWidth = 800;
|