@found-in-space/skykit 0.2.0-alpha.0 → 0.2.0-dev.20260527.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.
Files changed (42) hide show
  1. package/README.md +223 -8
  2. package/examples/custom-object-layer/custom-object-layer.js +1 -24
  3. package/examples/xr-free-roam/index.html +62 -4
  4. package/examples/xr-free-roam/xr-free-roam.css +249 -18
  5. package/examples/xr-free-roam/xr-free-roam.js +644 -217
  6. package/package.json +46 -5
  7. package/src/__tests__/skykit-anchored-images.test.js +32 -4
  8. package/src/__tests__/skykit-browser.test.js +442 -0
  9. package/src/__tests__/skykit-data.test.js +131 -0
  10. package/src/__tests__/skykit-parallax.test.js +4 -4
  11. package/src/__tests__/skykit-touch-os.test.js +71 -0
  12. package/src/__tests__/skykit-xr.test.js +123 -2
  13. package/src/__tests__/skykit.test.js +138 -1
  14. package/src/anchored-images.js +14 -15
  15. package/src/browser-addons.d.ts +16 -0
  16. package/src/browser-addons.js +155 -0
  17. package/src/browser-constellations.d.ts +13 -0
  18. package/src/browser-constellations.js +387 -0
  19. package/src/browser-journey.d.ts +8 -0
  20. package/src/browser-journey.js +240 -0
  21. package/src/browser.d.ts +170 -0
  22. package/src/browser.js +369 -0
  23. package/src/data.d.ts +133 -0
  24. package/src/data.js +447 -0
  25. package/src/embed.d.ts +6 -0
  26. package/src/embed.js +119 -0
  27. package/src/hr-diagram.js +23 -5
  28. package/src/index.d.ts +32 -7
  29. package/src/plugins.js +87 -43
  30. package/src/story.d.ts +57 -0
  31. package/src/story.js +396 -0
  32. package/src/three-shim.d.ts +32 -0
  33. package/src/touch-os.d.ts +70 -0
  34. package/src/touch-os.js +275 -0
  35. package/src/utils.js +96 -6
  36. package/src/viewer-entry.d.ts +10 -0
  37. package/src/viewer-entry.js +4 -0
  38. package/src/viewer.js +110 -12
  39. package/src/xr/plugins.js +224 -13
  40. package/src/xr/session.js +60 -14
  41. package/src/xr.d.ts +22 -0
  42. package/src/xr.js +1 -0
@@ -0,0 +1,240 @@
1
+ import { createJourney } from '@found-in-space/journey';
2
+ import { parseSpatialLookAtText } from '@found-in-space/spatial';
3
+
4
+ import { SKYKIT_ACTIONS } from './actions.js';
5
+ import {
6
+ createSkykitJourneyPlugin,
7
+ createSkykitNavigationPlugin,
8
+ } from './plugins.js';
9
+
10
+ const NAVIGATION_CAPABILITY = 'skykit:navigation';
11
+ const JOURNEY_CAPABILITY = 'skykit:browser.journey';
12
+ const SOURCE = 'browser.journey';
13
+
14
+ /**
15
+ * @param {{ browser: import('./browser.d.ts').SkykitBrowser }} context
16
+ * @returns {Promise<import('./browser.d.ts').SkykitBrowserJourneyFacade>}
17
+ */
18
+ export async function installSkykitJourneyBrowserCapability({ browser }) {
19
+ browser.capabilities.add(JOURNEY_CAPABILITY);
20
+
21
+ return {
22
+ transitionTo(viewOrScene, options) {
23
+ return transitionTo(browser, viewOrScene, options);
24
+ },
25
+ applyScene(sceneSpec) {
26
+ return applyScene(browser, sceneSpec);
27
+ },
28
+ load(input, options) {
29
+ return loadJourney(browser, input, options);
30
+ },
31
+ getSnapshot() {
32
+ return {
33
+ capability: JOURNEY_CAPABILITY,
34
+ navigationInstalled: browser.capabilities.has(NAVIGATION_CAPABILITY),
35
+ };
36
+ },
37
+ };
38
+ }
39
+
40
+ /**
41
+ * @param {import('./browser.d.ts').SkykitBrowser} browser
42
+ */
43
+ async function ensureNavigation(browser) {
44
+ if (browser.capabilities.has(NAVIGATION_CAPABILITY)) return;
45
+ await browser.install(createSkykitNavigationPlugin());
46
+ browser.capabilities.add(NAVIGATION_CAPABILITY);
47
+ }
48
+
49
+ /**
50
+ * @param {import('./browser.d.ts').SkykitBrowser} browser
51
+ * @param {unknown} viewOrScene
52
+ * @param {Record<string, unknown>} [options]
53
+ */
54
+ async function transitionTo(browser, viewOrScene, options = {}) {
55
+ await ensureNavigation(browser);
56
+ const payload = normalizeTransitionPayload(viewOrScene, options);
57
+ return browser.viewer.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, payload, { source: SOURCE });
58
+ }
59
+
60
+ /**
61
+ * @param {import('./browser.d.ts').SkykitBrowser} browser
62
+ * @param {unknown} sceneSpec
63
+ */
64
+ async function applyScene(browser, sceneSpec) {
65
+ const scene = /** @type {Record<string, unknown> | null} */ (
66
+ sceneSpec && typeof sceneSpec === 'object' ? sceneSpec : null
67
+ );
68
+ if (!scene) return null;
69
+ if (scene.view && typeof scene.view === 'object') {
70
+ browser.viewer.requestViewState(normalizeView(/** @type {Record<string, unknown>} */ (scene.view)), SOURCE);
71
+ }
72
+ const navigation = /** @type {Record<string, unknown> | null} */ (
73
+ scene.navigation && typeof scene.navigation === 'object' ? scene.navigation : null
74
+ );
75
+ if (navigation?.transitionTo) {
76
+ return transitionTo(browser, navigation.transitionTo);
77
+ }
78
+ return scene;
79
+ }
80
+
81
+ /**
82
+ * @param {import('./browser.d.ts').SkykitBrowser} browser
83
+ * @param {unknown} input
84
+ * @param {Record<string, unknown>} [options]
85
+ * @returns {Promise<import('./browser.d.ts').SkykitBrowserJourneyInstance>}
86
+ */
87
+ async function loadJourney(browser, input, options = {}) {
88
+ await ensureNavigation(browser);
89
+ const definition = await loadJourneyDefinition(input);
90
+ const plugin = isTimedJourney(definition)
91
+ ? createSkykitJourneyPlugin({
92
+ ...options,
93
+ timedJourney: definition,
94
+ })
95
+ : createSkykitJourneyPlugin({
96
+ ...options,
97
+ journey: createJourney(/** @type {import('@found-in-space/journey').CreateJourneyOptions} */ (definition)),
98
+ });
99
+ const uninstall = await browser.install(plugin);
100
+ let disposed = false;
101
+
102
+ return {
103
+ goTo(sceneId) {
104
+ assertActive();
105
+ return plugin.goTo?.(sceneId) ?? Promise.resolve(null);
106
+ },
107
+ next() {
108
+ assertActive();
109
+ return plugin.next?.() ?? Promise.resolve(null);
110
+ },
111
+ previous() {
112
+ assertActive();
113
+ return plugin.previous?.() ?? Promise.resolve(null);
114
+ },
115
+ play(payload) {
116
+ assertActive();
117
+ return plugin.play?.(payload) ?? 0;
118
+ },
119
+ pause() {
120
+ assertActive();
121
+ return plugin.pause?.() ?? 0;
122
+ },
123
+ seek(timeSecs) {
124
+ assertActive();
125
+ return plugin.seek?.({ timeSecs }) ?? 0;
126
+ },
127
+ getSnapshot() {
128
+ return {
129
+ disposed,
130
+ plugin: plugin.getSnapshot?.() ?? null,
131
+ };
132
+ },
133
+ dispose() {
134
+ if (disposed) return;
135
+ disposed = true;
136
+ uninstall();
137
+ },
138
+ };
139
+
140
+ function assertActive() {
141
+ if (disposed) throw new Error('SkyKit journey instance has been disposed.');
142
+ }
143
+ }
144
+
145
+ /**
146
+ * @param {unknown} input
147
+ * @returns {Promise<Record<string, unknown>>}
148
+ */
149
+ async function loadJourneyDefinition(input) {
150
+ if (typeof input === 'string') {
151
+ const response = await fetch(input);
152
+ if (!response.ok) throw new Error(`Failed to load SkyKit journey: ${response.status} ${response.statusText}`);
153
+ return /** @type {Record<string, unknown>} */ (await response.json());
154
+ }
155
+ if (!input || typeof input !== 'object') {
156
+ throw new TypeError('browser.journey.load() requires a URL or journey definition.');
157
+ }
158
+ return /** @type {Record<string, unknown>} */ (input);
159
+ }
160
+
161
+ /** @param {Record<string, unknown>} definition */
162
+ function isTimedJourney(definition) {
163
+ return Number.isFinite(Number(definition.durationSecs))
164
+ && (Array.isArray(definition.locationWaypoints)
165
+ || Array.isArray(definition.cameraLookWaypoints)
166
+ || Array.isArray(definition.cameraWaypoints));
167
+ }
168
+
169
+ /**
170
+ * @param {unknown} input
171
+ * @param {Record<string, unknown>} [options]
172
+ */
173
+ function normalizeTransitionPayload(input, options = {}) {
174
+ const source = /** @type {Record<string, unknown>} */ (
175
+ input && typeof input === 'object' ? input : { lookAt: input }
176
+ );
177
+ if (source.navigation && typeof source.navigation === 'object') {
178
+ const navigation = /** @type {Record<string, unknown>} */ (source.navigation);
179
+ if (navigation.transitionTo) return normalizeTransitionPayload(navigation.transitionTo, options);
180
+ }
181
+ const viewSource = source.view && typeof source.view === 'object'
182
+ ? /** @type {Record<string, unknown>} */ (source.view)
183
+ : source.to && typeof source.to === 'object'
184
+ ? /** @type {Record<string, unknown>} */ (source.to)
185
+ : stripTransitionOptions(source);
186
+ return {
187
+ ...stripUndefined({
188
+ ...source,
189
+ ...options,
190
+ view: normalizeView(viewSource),
191
+ durationSecs: options.durationSecs ?? source.durationSecs,
192
+ }),
193
+ };
194
+ }
195
+
196
+ /** @param {Record<string, unknown>} input */
197
+ function normalizeView(input) {
198
+ const lookAt = input.lookAt ?? (
199
+ hasLookAtShape(input) ? input : undefined
200
+ );
201
+ return {
202
+ ...input,
203
+ ...(lookAt !== undefined ? { lookAt: normalizeLookAt(lookAt) } : {}),
204
+ };
205
+ }
206
+
207
+ /** @param {unknown} input */
208
+ function normalizeLookAt(input) {
209
+ if (typeof input !== 'string') return input;
210
+ return parseSpatialLookAtText(input) ?? { star: input };
211
+ }
212
+
213
+ /** @param {Record<string, unknown>} input */
214
+ function stripTransitionOptions(input) {
215
+ const {
216
+ durationSecs: _durationSecs,
217
+ movement: _movement,
218
+ orientation: _orientation,
219
+ orientationDurationSecs: _orientationDurationSecs,
220
+ movementDurationSecs: _movementDurationSecs,
221
+ onArrive: _onArrive,
222
+ ...view
223
+ } = input;
224
+ return view;
225
+ }
226
+
227
+ /** @param {Record<string, unknown>} input */
228
+ function hasLookAtShape(input) {
229
+ return 'raDeg' in input
230
+ || 'raHours' in input
231
+ || 'decDeg' in input
232
+ || 'targetPc' in input
233
+ || 'orientationIcrs' in input
234
+ || 'star' in input;
235
+ }
236
+
237
+ /** @param {Record<string, unknown>} input */
238
+ function stripUndefined(input) {
239
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
240
+ }
@@ -0,0 +1,170 @@
1
+ import type { StarCellStrategy } from '@found-in-space/star-trees';
2
+ import type {
3
+ StarOctreeProviderService,
4
+ StarOctreeSessionOptions,
5
+ } from '@found-in-space/star-octree-provider';
6
+ import type { ThreeStarField } from '@found-in-space/three-star-field';
7
+ import type * as THREE from 'three';
8
+
9
+ import type {
10
+ Object3dLayerOptions,
11
+ SkykitAnimationLoop,
12
+ SkykitAnimationLoopOptions,
13
+ SkykitDragLookOptions,
14
+ SkykitLookAtInput,
15
+ SkykitKeyboardNavigationOptions,
16
+ SkykitPluginInput,
17
+ SkykitPluginTeardown,
18
+ SkykitThreePart,
19
+ SkykitViewState,
20
+ SkykitViewer,
21
+ Vector3Like,
22
+ } from './index.js';
23
+
24
+ export type SkykitBrowserHost = string | {
25
+ appendChild?: (node: unknown) => void;
26
+ removeChild?: (node: unknown) => void;
27
+ clientWidth?: number;
28
+ clientHeight?: number;
29
+ style?: { touchAction?: string };
30
+ };
31
+
32
+ export type SkykitBrowserStatusTarget = string | { textContent?: string | null };
33
+ export type SkykitBrowserMouseMode = 'grab' | 'look' | 'strafe' | 'none';
34
+ export type SkykitConstellationArtMode = 'off' | 'lazy' | 'preload';
35
+
36
+ export interface SkykitBrowserAddonContext {
37
+ id?: string;
38
+ host: SkykitBrowserHost | Element;
39
+ browser: SkykitBrowser;
40
+ viewer: SkykitViewer;
41
+ THREE: typeof THREE;
42
+ skykit: Record<string, unknown>;
43
+ }
44
+
45
+ export interface SkykitBrowserAddon {
46
+ id?: string;
47
+ install(
48
+ context: SkykitBrowserAddonContext
49
+ ): void | Promise<void> | SkykitPluginTeardown | Promise<SkykitPluginTeardown | void>;
50
+ }
51
+
52
+ export type SkykitBrowserInstallInput = SkykitPluginInput | SkykitBrowserAddon;
53
+
54
+ export interface SkykitBrowserGlobal {
55
+ browserAddons: SkykitBrowserAddon[];
56
+ registerBrowserAddon(addon: SkykitBrowserAddon): SkykitPluginTeardown;
57
+ whenReady(target?: string | Element): Promise<SkykitBrowser>;
58
+ getBrowsers(): SkykitBrowser[];
59
+ }
60
+
61
+ export interface SkykitBrowserJourneyFacade {
62
+ transitionTo(viewOrScene: unknown, options?: Record<string, unknown>): Promise<PromiseSettledResult<unknown>[]>;
63
+ applyScene(sceneSpec: unknown): Promise<unknown>;
64
+ load(input: string | Record<string, unknown>, options?: Record<string, unknown>): Promise<SkykitBrowserJourneyInstance>;
65
+ getSnapshot(): unknown | Promise<unknown>;
66
+ }
67
+
68
+ export interface SkykitBrowserJourneyInstance {
69
+ goTo(sceneId: string): Promise<unknown>;
70
+ next(): Promise<unknown>;
71
+ previous(): Promise<unknown>;
72
+ play(payload?: unknown): number;
73
+ pause(): number;
74
+ seek(timeSecs: number): number;
75
+ getSnapshot(): unknown;
76
+ dispose(): void;
77
+ }
78
+
79
+ export interface SkykitBrowserConstellationsOptions {
80
+ skyculture?: string;
81
+ manifest?: Record<string, unknown>;
82
+ manifestUrl?: string;
83
+ assetBaseUrl?: string;
84
+ art?: SkykitConstellationArtMode | string;
85
+ visible?: boolean;
86
+ priority?: number;
87
+ boundaryRadius?: number;
88
+ boundaryColor?: THREE.ColorRepresentation;
89
+ boundaryOpacity?: number;
90
+ renderOrder?: number;
91
+ artOpacity?: number;
92
+ artMaxAngleDeg?: number;
93
+ skipTextureErrors?: boolean;
94
+ }
95
+
96
+ export interface SkykitBrowserConstellationsFacade {
97
+ load(options?: SkykitBrowserConstellationsOptions): Promise<SkykitBrowserConstellationsFacade>;
98
+ show(): boolean | Promise<boolean>;
99
+ hide(): boolean | Promise<boolean>;
100
+ toggle(force?: boolean): boolean | Promise<boolean>;
101
+ setArt(mode: SkykitConstellationArtMode | string): SkykitConstellationArtMode | Promise<SkykitConstellationArtMode>;
102
+ getSnapshot(): unknown | Promise<unknown>;
103
+ dispose?(): void;
104
+ }
105
+
106
+ export interface SkykitBrowserOptions {
107
+ host?: SkykitBrowserHost;
108
+ status?: boolean | SkykitBrowserStatusTarget | null;
109
+ renderer?: THREE.WebGLRenderer;
110
+ camera?: THREE.PerspectiveCamera;
111
+ provider?: StarOctreeProviderService;
112
+ starField?: ThreeStarField;
113
+ octreeUrl?: string;
114
+ strategy?: StarCellStrategy;
115
+ session?: StarOctreeSessionOptions;
116
+ keyboard?: false | SkykitKeyboardNavigationOptions;
117
+ grab?: false | SkykitDragLookOptions;
118
+ mouseMode?: SkykitBrowserMouseMode;
119
+ plugins?: Iterable<SkykitPluginInput>;
120
+ view?: Partial<SkykitViewState>;
121
+ lookAt?: SkykitLookAtInput;
122
+ loop?: SkykitAnimationLoopOptions;
123
+ limitingMagnitude?: number;
124
+ exposure?: number;
125
+ coordinateUnitsPerParsec?: number;
126
+ speedPcPerSec?: number;
127
+ fovDeg?: number;
128
+ near?: number;
129
+ far?: number;
130
+ antialias?: boolean;
131
+ background?: THREE.ColorRepresentation;
132
+ maxDevicePixelRatio?: number;
133
+ autoStart?: boolean;
134
+ autoResize?: boolean;
135
+ autoDispose?: boolean;
136
+ disableTouchAction?: boolean;
137
+ }
138
+
139
+ export interface SkykitBrowser {
140
+ viewer: SkykitViewer;
141
+ renderer: THREE.WebGLRenderer;
142
+ camera: THREE.PerspectiveCamera;
143
+ provider: StarOctreeProviderService;
144
+ starField: ThreeStarField;
145
+ loop: SkykitAnimationLoop;
146
+ capabilities: Set<string>;
147
+ journey: SkykitBrowserJourneyFacade;
148
+ constellations: SkykitBrowserConstellationsFacade;
149
+ install(input: SkykitBrowserInstallInput): Promise<SkykitPluginTeardown>;
150
+ addObject(
151
+ object3d: THREE.Object3D,
152
+ options?: SkykitBrowserObjectOptions
153
+ ): SkykitBrowserObjectHandle;
154
+ resize(): void;
155
+ dispose(): Promise<void>;
156
+ }
157
+
158
+ export interface SkykitBrowserObjectOptions extends Omit<Object3dLayerOptions, 'object3d'> {
159
+ positionPc?: Vector3Like;
160
+ }
161
+
162
+ export interface SkykitBrowserObjectHandle {
163
+ object3d: THREE.Object3D;
164
+ part: SkykitThreePart;
165
+ remove: SkykitPluginTeardown;
166
+ dispose: SkykitPluginTeardown;
167
+ }
168
+
169
+ export declare function createSkykitBrowser(host: SkykitBrowserHost): Promise<SkykitBrowser>;
170
+ export declare function createSkykitBrowser(options?: SkykitBrowserOptions): Promise<SkykitBrowser>;