@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.
Files changed (40) hide show
  1. package/README.md +142 -11
  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 +675 -274
  6. package/package.json +22 -6
  7. package/src/__tests__/skykit-anchored-images.test.js +32 -4
  8. package/src/__tests__/skykit-browser.test.js +267 -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 +179 -2
  13. package/src/__tests__/skykit.test.js +142 -506
  14. package/src/actions.js +0 -8
  15. package/src/anchored-images.js +14 -15
  16. package/src/browser-addons.d.ts +16 -0
  17. package/src/browser-addons.js +155 -0
  18. package/src/browser-constellations.d.ts +13 -0
  19. package/src/browser-constellations.js +387 -0
  20. package/src/browser.d.ts +81 -0
  21. package/src/browser.js +192 -13
  22. package/src/data.d.ts +133 -0
  23. package/src/data.js +447 -0
  24. package/src/embed.d.ts +5 -0
  25. package/src/embed.js +53 -2
  26. package/src/hr-diagram.js +23 -5
  27. package/src/index.d.ts +21 -73
  28. package/src/index.js +0 -1
  29. package/src/plugins.js +22 -708
  30. package/src/three-shim.d.ts +32 -0
  31. package/src/touch-os.d.ts +70 -0
  32. package/src/touch-os.js +275 -0
  33. package/src/utils.js +96 -6
  34. package/src/viewer-entry.d.ts +10 -0
  35. package/src/viewer-entry.js +4 -0
  36. package/src/viewer.js +110 -12
  37. package/src/xr/plugins.js +298 -13
  38. package/src/xr/session.js +60 -14
  39. package/src/xr.d.ts +40 -0
  40. package/src/xr.js +2 -0
package/src/browser.d.ts CHANGED
@@ -7,13 +7,18 @@ import type { ThreeStarField } from '@found-in-space/three-star-field';
7
7
  import type * as THREE from 'three';
8
8
 
9
9
  import type {
10
+ Object3dLayerOptions,
10
11
  SkykitAnimationLoop,
11
12
  SkykitAnimationLoopOptions,
12
13
  SkykitDragLookOptions,
14
+ SkykitLookAtInput,
13
15
  SkykitKeyboardNavigationOptions,
14
16
  SkykitPluginInput,
17
+ SkykitPluginTeardown,
18
+ SkykitThreePart,
15
19
  SkykitViewState,
16
20
  SkykitViewer,
21
+ Vector3Like,
17
22
  } from './index.js';
18
23
 
19
24
  export type SkykitBrowserHost = string | {
@@ -25,6 +30,61 @@ export type SkykitBrowserHost = string | {
25
30
  };
26
31
 
27
32
  export type SkykitBrowserStatusTarget = string | { textContent?: string | null };
33
+ export type SkykitBrowserMouseMode = 'grab' | 'look' | 'strafe' | 'none';
34
+ export type SkykitConstellationArtMode = 'off' | 'lazy' | 'preload';
35
+ export type SkykitPersistentCacheMode = 'on' | 'off';
36
+
37
+ export interface SkykitBrowserAddonContext {
38
+ id?: string;
39
+ host: SkykitBrowserHost | Element;
40
+ browser: SkykitBrowser;
41
+ viewer: SkykitViewer;
42
+ THREE: typeof THREE;
43
+ skykit: Record<string, unknown>;
44
+ }
45
+
46
+ export interface SkykitBrowserAddon {
47
+ id?: string;
48
+ install(
49
+ context: SkykitBrowserAddonContext
50
+ ): void | Promise<void> | SkykitPluginTeardown | Promise<SkykitPluginTeardown | void>;
51
+ }
52
+
53
+ export type SkykitBrowserInstallInput = SkykitPluginInput | SkykitBrowserAddon;
54
+
55
+ export interface SkykitBrowserGlobal {
56
+ browserAddons: SkykitBrowserAddon[];
57
+ registerBrowserAddon(addon: SkykitBrowserAddon): SkykitPluginTeardown;
58
+ whenReady(target?: string | Element): Promise<SkykitBrowser>;
59
+ getBrowsers(): SkykitBrowser[];
60
+ }
61
+
62
+ export interface SkykitBrowserConstellationsOptions {
63
+ skyculture?: string;
64
+ manifest?: Record<string, unknown>;
65
+ manifestUrl?: string;
66
+ assetBaseUrl?: string;
67
+ art?: SkykitConstellationArtMode | string;
68
+ visible?: boolean;
69
+ priority?: number;
70
+ boundaryRadius?: number;
71
+ boundaryColor?: THREE.ColorRepresentation;
72
+ boundaryOpacity?: number;
73
+ renderOrder?: number;
74
+ artOpacity?: number;
75
+ artMaxAngleDeg?: number;
76
+ skipTextureErrors?: boolean;
77
+ }
78
+
79
+ export interface SkykitBrowserConstellationsFacade {
80
+ load(options?: SkykitBrowserConstellationsOptions): Promise<SkykitBrowserConstellationsFacade>;
81
+ show(): boolean | Promise<boolean>;
82
+ hide(): boolean | Promise<boolean>;
83
+ toggle(force?: boolean): boolean | Promise<boolean>;
84
+ setArt(mode: SkykitConstellationArtMode | string): SkykitConstellationArtMode | Promise<SkykitConstellationArtMode>;
85
+ getSnapshot(): unknown | Promise<unknown>;
86
+ dispose?(): void;
87
+ }
28
88
 
29
89
  export interface SkykitBrowserOptions {
30
90
  host?: SkykitBrowserHost;
@@ -34,12 +94,15 @@ export interface SkykitBrowserOptions {
34
94
  provider?: StarOctreeProviderService;
35
95
  starField?: ThreeStarField;
36
96
  octreeUrl?: string;
97
+ persistentCache?: SkykitPersistentCacheMode | string;
37
98
  strategy?: StarCellStrategy;
38
99
  session?: StarOctreeSessionOptions;
39
100
  keyboard?: false | SkykitKeyboardNavigationOptions;
40
101
  grab?: false | SkykitDragLookOptions;
102
+ mouseMode?: SkykitBrowserMouseMode;
41
103
  plugins?: Iterable<SkykitPluginInput>;
42
104
  view?: Partial<SkykitViewState>;
105
+ lookAt?: SkykitLookAtInput;
43
106
  loop?: SkykitAnimationLoopOptions;
44
107
  limitingMagnitude?: number;
45
108
  exposure?: number;
@@ -64,9 +127,27 @@ export interface SkykitBrowser {
64
127
  provider: StarOctreeProviderService;
65
128
  starField: ThreeStarField;
66
129
  loop: SkykitAnimationLoop;
130
+ capabilities: Set<string>;
131
+ constellations: SkykitBrowserConstellationsFacade;
132
+ install(input: SkykitBrowserInstallInput): Promise<SkykitPluginTeardown>;
133
+ addObject(
134
+ object3d: THREE.Object3D,
135
+ options?: SkykitBrowserObjectOptions
136
+ ): SkykitBrowserObjectHandle;
67
137
  resize(): void;
68
138
  dispose(): Promise<void>;
69
139
  }
70
140
 
141
+ export interface SkykitBrowserObjectOptions extends Omit<Object3dLayerOptions, 'object3d'> {
142
+ positionPc?: Vector3Like;
143
+ }
144
+
145
+ export interface SkykitBrowserObjectHandle {
146
+ object3d: THREE.Object3D;
147
+ part: SkykitThreePart;
148
+ remove: SkykitPluginTeardown;
149
+ dispose: SkykitPluginTeardown;
150
+ }
151
+
71
152
  export declare function createSkykitBrowser(host: SkykitBrowserHost): Promise<SkykitBrowser>;
72
153
  export declare function createSkykitBrowser(options?: SkykitBrowserOptions): Promise<SkykitBrowser>;
package/src/browser.js CHANGED
@@ -8,12 +8,17 @@ import { createObserverShellStrategy } from '@found-in-space/star-trees';
8
8
  import { createThreeStarField } from '@found-in-space/three-star-field';
9
9
 
10
10
  import { createSkykitAnimationLoop } from './animation-loop.js';
11
+ import { SKYKIT_ACTIONS, SKYKIT_CONTROLS } from './actions.js';
11
12
  import {
12
13
  createKeyboardNavigationPlugin,
14
+ createObject3dPlugin,
15
+ createMouseLookPlugin,
16
+ createSkykitNavigationPlugin,
13
17
  createSkyGrabPlugin,
14
18
  createSkykitStatusPlugin,
15
19
  createStreamingStarsPlugin,
16
20
  } from './plugins.js';
21
+ import { createObject3dLayer } from './layers.js';
17
22
  import { createSkykitViewer } from './viewer.js';
18
23
 
19
24
  const DEFAULT_LIMITING_MAGNITUDE = 6.5;
@@ -46,6 +51,7 @@ export async function createSkykitBrowser(input = {}) {
46
51
  );
47
52
  const provider = options.provider ?? createStarOctreeProviderService({
48
53
  url: options.octreeUrl ?? OCTREE_DEFAULT,
54
+ persistentCache: normalizePersistentCacheMode(options.persistentCache),
49
55
  });
50
56
  const starField = options.starField ?? createThreeStarField({
51
57
  limitingMagnitude,
@@ -63,6 +69,7 @@ export async function createSkykitBrowser(input = {}) {
63
69
  observerPc: { x: 0, y: 0, z: 0 },
64
70
  coordinateUnitsPerParsec: positive(options.coordinateUnitsPerParsec, DEFAULT_UNITS_PER_PARSEC),
65
71
  limitingMagnitude,
72
+ ...(options.lookAt ? { lookAt: options.lookAt } : {}),
66
73
  ...(options.view ?? {}),
67
74
  },
68
75
  plugins: [
@@ -81,41 +88,56 @@ export async function createSkykitBrowser(input = {}) {
81
88
  ...(options.keyboard ?? {}),
82
89
  }),
83
90
  ]),
84
- ...(options.grab === false ? [] : [
85
- createSkyGrabPlugin({
86
- target: host,
87
- sensitivityRadiansPerPixel: 0.00075,
88
- ...(options.grab ?? {}),
89
- }),
90
- ]),
91
+ ...createPointerPlugins(options, host),
91
92
  ...(statusTarget ? [createStatusPlugin(statusTarget)] : []),
92
93
  ...(options.plugins ?? []),
93
94
  ],
94
95
  });
95
96
 
96
97
  const loop = createSkykitAnimationLoop(viewer, options.loop);
98
+ const capabilities = new Set();
99
+ /** @type {Array<() => void | Promise<void>>} */
100
+ const browserDisposables = [];
97
101
  let disposed = false;
102
+ /** @type {import('./browser.d.ts').SkykitBrowser} */
103
+ const browser = {
104
+ viewer,
105
+ renderer,
106
+ camera,
107
+ provider,
108
+ starField,
109
+ loop,
110
+ capabilities,
111
+ install,
112
+ addObject,
113
+ resize,
114
+ dispose,
115
+ };
116
+ browser.constellations = createLazyConstellationsFacade(browser, host);
98
117
 
99
- const resize = () => {
118
+ function resize() {
100
119
  viewer.resize({
101
120
  devicePixelRatio: Math.min(
102
121
  window.devicePixelRatio || 1,
103
122
  positive(options.maxDevicePixelRatio, DEFAULT_MAX_DEVICE_PIXEL_RATIO),
104
123
  ),
105
124
  });
106
- };
107
- const dispose = async () => {
125
+ }
126
+ async function dispose() {
108
127
  if (disposed) return;
109
128
  disposed = true;
110
129
  window.removeEventListener('resize', resize);
111
130
  window.removeEventListener('pagehide', disposeSoon);
112
131
  window.removeEventListener('beforeunload', disposeSoon);
132
+ for (const disposable of browserDisposables.splice(0).reverse()) {
133
+ await disposable();
134
+ }
113
135
  loop.dispose();
114
136
  await viewer.dispose();
115
137
  if (!options.provider) await provider.dispose?.();
116
138
  if (!options.renderer) renderer.dispose?.();
117
- };
118
- const disposeSoon = () => { void dispose(); };
139
+ }
140
+ function disposeSoon() { void dispose(); }
119
141
 
120
142
  if (options.autoResize !== false) window.addEventListener('resize', resize);
121
143
  if (options.autoDispose !== false) {
@@ -125,7 +147,151 @@ export async function createSkykitBrowser(input = {}) {
125
147
  resize();
126
148
  if (options.autoStart !== false) loop.start();
127
149
 
128
- return { viewer, renderer, camera, provider, starField, loop, resize, dispose };
150
+ return browser;
151
+
152
+ /**
153
+ * @param {import('./browser.d.ts').SkykitBrowserInstallInput} input
154
+ * @returns {Promise<import('./index.d.ts').SkykitPluginTeardown>}
155
+ */
156
+ async function install(input) {
157
+ if (!input) return () => {};
158
+ const teardown = isBrowserAddon(input)
159
+ ? await input.install(createBrowserAddonContext(input))
160
+ : await viewer.addPlugin(/** @type {import('./index.d.ts').SkykitPluginInput} */ (input));
161
+ if (typeof teardown !== 'function') return () => {};
162
+ browserDisposables.push(teardown);
163
+ return () => {
164
+ const index = browserDisposables.indexOf(teardown);
165
+ if (index >= 0) browserDisposables.splice(index, 1);
166
+ void teardown();
167
+ };
168
+ }
169
+
170
+ /**
171
+ * @param {import('./browser.d.ts').SkykitBrowserAddon} addon
172
+ * @returns {import('./browser.d.ts').SkykitBrowserAddonContext}
173
+ */
174
+ function createBrowserAddonContext(addon) {
175
+ return {
176
+ id: addon.id,
177
+ host,
178
+ browser,
179
+ viewer,
180
+ THREE,
181
+ skykit: {
182
+ SKYKIT_ACTIONS,
183
+ SKYKIT_CONTROLS,
184
+ createObject3dPlugin,
185
+ createSkykitNavigationPlugin,
186
+ },
187
+ };
188
+ }
189
+
190
+ /**
191
+ * @param {THREE.Object3D} object3d
192
+ * @param {import('./browser.d.ts').SkykitBrowserObjectOptions} [objectOptions]
193
+ * @returns {import('./browser.d.ts').SkykitBrowserObjectHandle}
194
+ */
195
+ function addObject(object3d, objectOptions = {}) {
196
+ if (!object3d) {
197
+ throw new TypeError('SkykitBrowser.addObject() requires a THREE.Object3D.');
198
+ }
199
+ const { positionPc, ...layerOptions } = objectOptions;
200
+ if (positionPc) {
201
+ setObjectPositionPc(
202
+ object3d,
203
+ positionPc,
204
+ viewer.getViewState().coordinateUnitsPerParsec,
205
+ );
206
+ }
207
+ const part = createObject3dLayer({
208
+ ...layerOptions,
209
+ object3d,
210
+ anchorMode: layerOptions.anchorMode ?? 'world-space',
211
+ });
212
+ const remove = viewer.addPart(part);
213
+ return {
214
+ object3d,
215
+ part,
216
+ remove,
217
+ dispose: remove,
218
+ };
219
+ }
220
+ }
221
+
222
+ /** @param {unknown} input */
223
+ function isBrowserAddon(input) {
224
+ return Boolean(input && typeof input === 'object' && typeof /** @type {{ install?: unknown }} */ (input).install === 'function');
225
+ }
226
+
227
+ /**
228
+ * @param {import('./browser.d.ts').SkykitBrowser} browser
229
+ * @param {Element | import('./browser.d.ts').SkykitBrowserHost} host
230
+ * @returns {import('./browser.d.ts').SkykitBrowserConstellationsFacade}
231
+ */
232
+ function createLazyConstellationsFacade(browser, host) {
233
+ /** @type {Promise<import('./browser.d.ts').SkykitBrowserConstellationsFacade> | null} */
234
+ let loaded = null;
235
+ const loadCapability = (options = {}) => {
236
+ loaded ??= import('./browser-constellations.js')
237
+ .then((module) => module.installSkykitConstellationsBrowserCapability({
238
+ browser,
239
+ host,
240
+ options,
241
+ }));
242
+ return loaded;
243
+ };
244
+ return {
245
+ async load(options) {
246
+ return loadCapability(options);
247
+ },
248
+ async show() {
249
+ return (await loadCapability()).show();
250
+ },
251
+ async hide() {
252
+ return (await loadCapability()).hide();
253
+ },
254
+ async toggle(force) {
255
+ return (await loadCapability()).toggle(force);
256
+ },
257
+ async setArt(mode) {
258
+ return (await loadCapability()).setArt(mode);
259
+ },
260
+ async getSnapshot() {
261
+ return (await loadCapability()).getSnapshot();
262
+ },
263
+ };
264
+ }
265
+
266
+ function createPointerPlugins(options, host) {
267
+ const mouseMode = normalizeMouseMode(options.mouseMode);
268
+ if (options.grab === false || mouseMode === 'none') return [];
269
+ const pointerOptions = {
270
+ target: host,
271
+ sensitivityRadiansPerPixel: 0.00075,
272
+ ...(options.grab ?? {}),
273
+ };
274
+ return [
275
+ mouseMode === 'look' || mouseMode === 'strafe'
276
+ ? createMouseLookPlugin(pointerOptions)
277
+ : createSkyGrabPlugin(pointerOptions),
278
+ ];
279
+ }
280
+
281
+ function normalizeMouseMode(value) {
282
+ const mode = String(value ?? 'grab').trim().toLowerCase();
283
+ if (mode === 'look' || mode === 'mouse-look' || mode === 'mouselook' || mode === 'game' || mode === 'strafe') {
284
+ return mode === 'strafe' ? 'strafe' : 'look';
285
+ }
286
+ if (mode === 'none' || mode === 'off' || mode === 'false') return 'none';
287
+ return 'grab';
288
+ }
289
+
290
+ function normalizePersistentCacheMode(value) {
291
+ const mode = String(value ?? 'on').trim().toLowerCase();
292
+ return mode === 'off' || mode === 'false' || mode === 'no' || mode === '0' || mode === 'disabled'
293
+ ? 'off'
294
+ : 'on';
129
295
  }
130
296
 
131
297
  function createStatusPlugin(target) {
@@ -165,3 +331,16 @@ function positive(value, fallback) {
165
331
  const number = Number(value);
166
332
  return Number.isFinite(number) && number > 0 ? number : fallback;
167
333
  }
334
+
335
+ /**
336
+ * @param {THREE.Object3D} object3d
337
+ * @param {{ x: number; y: number; z: number }} positionPc
338
+ * @param {number} unitsPerParsec
339
+ */
340
+ function setObjectPositionPc(object3d, positionPc, unitsPerParsec) {
341
+ object3d.position?.set?.(
342
+ positionPc.x * unitsPerParsec,
343
+ positionPc.y * unitsPerParsec,
344
+ positionPc.z * unitsPerParsec,
345
+ );
346
+ }
package/src/data.d.ts ADDED
@@ -0,0 +1,133 @@
1
+ import type { MetaSidecarEntry, MetaSidecarProviderService } from '@found-in-space/meta-sidecar-provider';
2
+ import type {
3
+ StarOctreeCellStreamOptions,
4
+ StarOctreeProviderService,
5
+ StarOctreeProviderServiceOptions,
6
+ } from '@found-in-space/star-octree-provider';
7
+ import type {
8
+ StarObjectRef,
9
+ StarTreePointPc,
10
+ } from '@found-in-space/star-trees';
11
+
12
+ export {
13
+ OCTREE_DEFAULT,
14
+ createStarOctreeProviderService,
15
+ } from '@found-in-space/star-octree-provider';
16
+ export {
17
+ createObserverShellStrategy,
18
+ createSphereVolumeStrategy,
19
+ createStarCellKey,
20
+ decodeTemperatureK,
21
+ temperatureToRgb,
22
+ } from '@found-in-space/star-trees';
23
+ export {
24
+ createMetaSidecarProviderService,
25
+ deriveMetaSidecarUrlFromRenderUrl,
26
+ metaSidecarEntryDisplayFields,
27
+ } from '@found-in-space/meta-sidecar-provider';
28
+
29
+ export interface SkykitStarRow {
30
+ ref: StarObjectRef | null;
31
+ cellKey: string;
32
+ level: number;
33
+ mortonCode: string;
34
+ ordinal: number;
35
+ positionPc: StarTreePointPc;
36
+ xPc: number;
37
+ yPc: number;
38
+ zPc: number;
39
+ distancePc: number;
40
+ magAbs: number | null;
41
+ absoluteMagnitude: number | null;
42
+ apparentMagnitude: number | null;
43
+ teffLog8: number | null;
44
+ temperatureK: number | null;
45
+ }
46
+
47
+ export interface SkykitRowsFromCellsOptions {
48
+ observerPc?: StarTreePointPc;
49
+ limitingMagnitude?: number;
50
+ filterVisible?: boolean;
51
+ }
52
+
53
+ export interface SkykitStarDataOptions extends Omit<StarOctreeCellStreamOptions, 'view' | 'strategy'> {
54
+ provider?: StarOctreeProviderService;
55
+ providerId?: string;
56
+ octreeUrl?: string;
57
+ persistentCache?: StarOctreeProviderServiceOptions['persistentCache'];
58
+ limits?: StarOctreeProviderServiceOptions['limits'];
59
+ observerPc?: StarTreePointPc;
60
+ centerPc?: StarTreePointPc;
61
+ radiusPc?: number;
62
+ limitingMagnitude?: number;
63
+ maxStars?: number;
64
+ filterVisible?: boolean;
65
+ sortBy?:
66
+ | 'apparentMagnitude'
67
+ | 'distancePc'
68
+ | 'magAbs'
69
+ | 'absoluteMagnitude'
70
+ | null
71
+ | false
72
+ | ((left: SkykitStarRow, right: SkykitStarRow) => number);
73
+ view?: StarOctreeCellStreamOptions['view'];
74
+ strategy?: StarOctreeCellStreamOptions['strategy'];
75
+ }
76
+
77
+ export type SkykitStarLabelInput = SkykitStarRow | StarObjectRef;
78
+
79
+ export interface SkykitStarLabelOptions {
80
+ metaProvider?: MetaSidecarProviderService;
81
+ metaProviderId?: string;
82
+ metaUrl?: string;
83
+ parentDatasetId?: string;
84
+ datasetId?: string;
85
+ persistentCache?: 'on' | 'off';
86
+ metaLimits?: {
87
+ shardPrefetchBytes?: number;
88
+ };
89
+ provider?: StarOctreeProviderService;
90
+ octreeUrl?: string;
91
+ }
92
+
93
+ export interface SkykitStarLabel {
94
+ star: SkykitStarLabelInput;
95
+ ref: StarObjectRef | null;
96
+ entry: MetaSidecarEntry | null;
97
+ fields: {
98
+ properName: string;
99
+ bayer: string;
100
+ hd: string;
101
+ hip: string;
102
+ gaia: string;
103
+ primaryLabel: string;
104
+ };
105
+ label: string;
106
+ }
107
+
108
+ export declare function createStarStream(
109
+ options?: SkykitStarDataOptions
110
+ ): AsyncIterable<SkykitStarRow[]>;
111
+
112
+ export declare function loadStarRows(
113
+ options?: SkykitStarDataOptions
114
+ ): Promise<SkykitStarRow[]>;
115
+
116
+ export declare function streamStarRows(
117
+ options?: SkykitStarDataOptions
118
+ ): AsyncIterable<SkykitStarRow[]>;
119
+
120
+ export declare function rowsFromStarCells(
121
+ cells: Iterable<import('@found-in-space/star-trees').StarCellData>,
122
+ options?: SkykitRowsFromCellsOptions
123
+ ): SkykitStarRow[];
124
+
125
+ export declare function loadStarLabels(
126
+ input: Iterable<SkykitStarLabelInput> | SkykitStarDataOptions,
127
+ options?: SkykitStarLabelOptions
128
+ ): Promise<SkykitStarLabel[]>;
129
+
130
+ export declare function formatStarLabel(
131
+ entry?: MetaSidecarEntry | null,
132
+ fallback?: string
133
+ ): string;