@found-in-space/skykit 0.2.0-alpha.1 → 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.
- package/README.md +143 -6
- 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 +644 -217
- package/package.json +31 -5
- package/src/__tests__/skykit-anchored-images.test.js +32 -4
- package/src/__tests__/skykit-browser.test.js +217 -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 +123 -2
- package/src/__tests__/skykit.test.js +138 -1
- 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-journey.d.ts +8 -0
- package/src/browser-journey.js +240 -0
- package/src/browser.d.ts +98 -0
- package/src/browser.js +215 -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 +52 -2
- package/src/hr-diagram.js +23 -5
- package/src/index.d.ts +32 -7
- package/src/plugins.js +87 -43
- package/src/story.d.ts +57 -0
- package/src/story.js +396 -0
- 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 +224 -13
- package/src/xr/session.js +60 -14
- package/src/xr.d.ts +22 -0
- package/src/xr.js +1 -0
package/src/data.js
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OCTREE_DEFAULT,
|
|
3
|
+
createStarOctreeProviderService,
|
|
4
|
+
} from '@found-in-space/star-octree-provider';
|
|
5
|
+
import {
|
|
6
|
+
createObserverShellStrategy,
|
|
7
|
+
createSphereVolumeStrategy,
|
|
8
|
+
createStarCellKey,
|
|
9
|
+
decodeTemperatureK,
|
|
10
|
+
temperatureToRgb,
|
|
11
|
+
} from '@found-in-space/star-trees';
|
|
12
|
+
import {
|
|
13
|
+
createMetaSidecarProviderService,
|
|
14
|
+
deriveMetaSidecarUrlFromRenderUrl,
|
|
15
|
+
metaSidecarEntryDisplayFields,
|
|
16
|
+
} from '@found-in-space/meta-sidecar-provider';
|
|
17
|
+
|
|
18
|
+
const DEFAULT_CENTER_PC = Object.freeze({ x: 0, y: 0, z: 0 });
|
|
19
|
+
const DEFAULT_LIMITING_MAGNITUDE = 6.5;
|
|
20
|
+
const DEFAULT_STAR_ATTRIBUTES = Object.freeze(['position', 'magAbs', 'teffLog8', 'objectRef']);
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
OCTREE_DEFAULT,
|
|
24
|
+
createMetaSidecarProviderService,
|
|
25
|
+
createObserverShellStrategy,
|
|
26
|
+
createSphereVolumeStrategy,
|
|
27
|
+
createStarCellKey,
|
|
28
|
+
createStarOctreeProviderService,
|
|
29
|
+
decodeTemperatureK,
|
|
30
|
+
deriveMetaSidecarUrlFromRenderUrl,
|
|
31
|
+
metaSidecarEntryDisplayFields,
|
|
32
|
+
temperatureToRgb,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Alias for the row-batch stream used by beginner examples.
|
|
37
|
+
*
|
|
38
|
+
* @param {import('./data.d.ts').SkykitStarDataOptions} [options]
|
|
39
|
+
* @returns {AsyncIterable<import('./data.d.ts').SkykitStarRow[]>}
|
|
40
|
+
*/
|
|
41
|
+
export function createStarStream(options = {}) {
|
|
42
|
+
return streamStarRows(options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Load star rows for a list, map, game, or custom renderer.
|
|
47
|
+
*
|
|
48
|
+
* @param {import('./data.d.ts').SkykitStarDataOptions} [options]
|
|
49
|
+
* @returns {Promise<import('./data.d.ts').SkykitStarRow[]>}
|
|
50
|
+
*/
|
|
51
|
+
export async function loadStarRows(options = {}) {
|
|
52
|
+
/** @type {import('./data.d.ts').SkykitStarRow[]} */
|
|
53
|
+
const rows = [];
|
|
54
|
+
for await (const batch of streamStarRows({
|
|
55
|
+
...options,
|
|
56
|
+
maxStars: undefined,
|
|
57
|
+
sortBy: null,
|
|
58
|
+
})) {
|
|
59
|
+
rows.push(...batch);
|
|
60
|
+
}
|
|
61
|
+
return selectRows(rows, options);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Stream star rows as plain JavaScript batches. The stream completes when the
|
|
66
|
+
* provider reports that the requested current cell set is loaded.
|
|
67
|
+
*
|
|
68
|
+
* @param {import('./data.d.ts').SkykitStarDataOptions} [options]
|
|
69
|
+
* @returns {AsyncIterable<import('./data.d.ts').SkykitStarRow[]>}
|
|
70
|
+
*/
|
|
71
|
+
export async function* streamStarRows(options = {}) {
|
|
72
|
+
const { provider, disposeProvider } = createProvider(options);
|
|
73
|
+
const observerPc = resolveObserverPc(options);
|
|
74
|
+
const filterVisible = options.filterVisible !== false;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
for await (const delta of provider.streamCells(createCellStreamOptions(options, observerPc))) {
|
|
78
|
+
if (delta.type === 'stars/cells-upsert') {
|
|
79
|
+
const rows = rowsFromStarCells(delta.cells, {
|
|
80
|
+
observerPc,
|
|
81
|
+
limitingMagnitude: options.limitingMagnitude,
|
|
82
|
+
filterVisible,
|
|
83
|
+
});
|
|
84
|
+
if (rows.length > 0) yield rows;
|
|
85
|
+
}
|
|
86
|
+
if (delta.type === 'stars/error') {
|
|
87
|
+
throw new Error(delta.error?.message ?? 'SkyKit star stream failed.');
|
|
88
|
+
}
|
|
89
|
+
if (delta.type === 'stars/current') {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} finally {
|
|
94
|
+
if (disposeProvider) await provider.dispose?.();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert provider star cells into plain rows without owning any renderer.
|
|
100
|
+
*
|
|
101
|
+
* @param {Iterable<import('@found-in-space/star-trees').StarCellData>} cells
|
|
102
|
+
* @param {import('./data.d.ts').SkykitRowsFromCellsOptions} [options]
|
|
103
|
+
* @returns {import('./data.d.ts').SkykitStarRow[]}
|
|
104
|
+
*/
|
|
105
|
+
export function rowsFromStarCells(cells, options = {}) {
|
|
106
|
+
/** @type {import('./data.d.ts').SkykitStarRow[]} */
|
|
107
|
+
const rows = [];
|
|
108
|
+
const observerPc = normalizePoint(options.observerPc, DEFAULT_CENTER_PC);
|
|
109
|
+
const limitingMagnitude = finiteNumber(options.limitingMagnitude, Number.POSITIVE_INFINITY);
|
|
110
|
+
const filterVisible = options.filterVisible === true;
|
|
111
|
+
|
|
112
|
+
for (const cell of cells) {
|
|
113
|
+
rows.push(...rowsFromStarCell(cell, { observerPc, limitingMagnitude, filterVisible }));
|
|
114
|
+
}
|
|
115
|
+
return rows;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Load display labels for rows or refs from the metadata sidecar.
|
|
120
|
+
*
|
|
121
|
+
* @param {Iterable<import('./data.d.ts').SkykitStarLabelInput> | import('./data.d.ts').SkykitStarDataOptions} input
|
|
122
|
+
* @param {import('./data.d.ts').SkykitStarLabelOptions} [options]
|
|
123
|
+
* @returns {Promise<import('./data.d.ts').SkykitStarLabel[]>}
|
|
124
|
+
*/
|
|
125
|
+
export async function loadStarLabels(input, options = {}) {
|
|
126
|
+
const stars = isIterable(input)
|
|
127
|
+
? Array.from(input)
|
|
128
|
+
: await loadStarRows(/** @type {import('./data.d.ts').SkykitStarDataOptions} */ (input ?? {}));
|
|
129
|
+
const refs = stars.map(resolveStarRef);
|
|
130
|
+
const { metaProvider, disposeMetaProvider } = await createLabelProvider(refs, {
|
|
131
|
+
...options,
|
|
132
|
+
provider: options.provider ?? (!isIterable(input) ? input?.provider : undefined),
|
|
133
|
+
octreeUrl: options.octreeUrl ?? (!isIterable(input) ? input?.octreeUrl : undefined),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
return await Promise.all(stars.map(async (star, index) => {
|
|
138
|
+
const ref = refs[index] ?? null;
|
|
139
|
+
const entry = ref ? await metaProvider.getMeta(ref) : null;
|
|
140
|
+
const fallback = ref ? formatStarRef(ref) : 'Unnamed star';
|
|
141
|
+
const fields = metaSidecarEntryDisplayFields(entry);
|
|
142
|
+
return {
|
|
143
|
+
star,
|
|
144
|
+
ref,
|
|
145
|
+
entry,
|
|
146
|
+
fields,
|
|
147
|
+
label: fields.primaryLabel || formatStarLabel(entry, fallback),
|
|
148
|
+
};
|
|
149
|
+
}));
|
|
150
|
+
} finally {
|
|
151
|
+
if (disposeMetaProvider) metaProvider.dispose?.();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {import('@found-in-space/meta-sidecar-provider').MetaSidecarEntry | null | undefined} entry
|
|
157
|
+
* @param {string} [fallback]
|
|
158
|
+
* @returns {string}
|
|
159
|
+
*/
|
|
160
|
+
export function formatStarLabel(entry, fallback = 'Unnamed star') {
|
|
161
|
+
const fields = metaSidecarEntryDisplayFields(entry);
|
|
162
|
+
return fields.primaryLabel || fields.properName || fields.bayer || fields.hd || fields.hip || fields.gaia || fallback;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param {import('@found-in-space/star-trees').StarCellData} cell
|
|
167
|
+
* @param {{
|
|
168
|
+
* observerPc: { x: number; y: number; z: number };
|
|
169
|
+
* limitingMagnitude: number;
|
|
170
|
+
* filterVisible: boolean;
|
|
171
|
+
* }} options
|
|
172
|
+
* @returns {import('./data.d.ts').SkykitStarRow[]}
|
|
173
|
+
*/
|
|
174
|
+
function rowsFromStarCell(cell, options) {
|
|
175
|
+
const positions = cell.coordinates?.components;
|
|
176
|
+
if (!positions) return [];
|
|
177
|
+
const magAbs = cell.attributes?.magAbs ?? null;
|
|
178
|
+
const teffLog8 = cell.attributes?.teffLog8 ?? null;
|
|
179
|
+
const refs = cell.refs ?? [];
|
|
180
|
+
/** @type {import('./data.d.ts').SkykitStarRow[]} */
|
|
181
|
+
const rows = [];
|
|
182
|
+
|
|
183
|
+
for (let index = 0; index < cell.count; index += 1) {
|
|
184
|
+
const positionPc = {
|
|
185
|
+
x: positions[index * 3],
|
|
186
|
+
y: positions[index * 3 + 1],
|
|
187
|
+
z: positions[index * 3 + 2],
|
|
188
|
+
};
|
|
189
|
+
const distancePc = distanceBetween(positionPc, options.observerPc);
|
|
190
|
+
const absoluteMagnitude = magAbs?.[index] ?? null;
|
|
191
|
+
const apparentMagnitude = absoluteMagnitude == null
|
|
192
|
+
? null
|
|
193
|
+
: apparentMagnitudeFromAbsolute(absoluteMagnitude, distancePc);
|
|
194
|
+
if (
|
|
195
|
+
options.filterVisible &&
|
|
196
|
+
apparentMagnitude != null &&
|
|
197
|
+
apparentMagnitude > options.limitingMagnitude
|
|
198
|
+
) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const temperatureByte = teffLog8?.[index] ?? null;
|
|
202
|
+
const ref = refs[index] ?? null;
|
|
203
|
+
rows.push({
|
|
204
|
+
ref,
|
|
205
|
+
cellKey: cell.cellKey ?? createStarCellKey(cell.cell),
|
|
206
|
+
level: cell.cell?.level ?? ref?.level ?? 0,
|
|
207
|
+
mortonCode: cell.cell?.mortonCode ?? ref?.mortonCode ?? '',
|
|
208
|
+
ordinal: ref?.ordinal ?? index,
|
|
209
|
+
positionPc,
|
|
210
|
+
xPc: positionPc.x,
|
|
211
|
+
yPc: positionPc.y,
|
|
212
|
+
zPc: positionPc.z,
|
|
213
|
+
distancePc,
|
|
214
|
+
magAbs: absoluteMagnitude,
|
|
215
|
+
absoluteMagnitude,
|
|
216
|
+
apparentMagnitude,
|
|
217
|
+
teffLog8: temperatureByte,
|
|
218
|
+
temperatureK: temperatureByte == null ? null : decodeTemperatureK(temperatureByte),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return rows;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @param {import('./data.d.ts').SkykitStarDataOptions} options
|
|
227
|
+
* @param {{ x: number; y: number; z: number }} observerPc
|
|
228
|
+
* @returns {import('@found-in-space/star-octree-provider').StarOctreeCellStreamOptions}
|
|
229
|
+
*/
|
|
230
|
+
function createCellStreamOptions(options, observerPc) {
|
|
231
|
+
const centerPc = normalizePoint(options.centerPc, observerPc);
|
|
232
|
+
const limitingMagnitude = finiteNumber(options.limitingMagnitude, DEFAULT_LIMITING_MAGNITUDE);
|
|
233
|
+
const strategy = options.strategy ?? (
|
|
234
|
+
positiveNumber(options.radiusPc, 0) > 0
|
|
235
|
+
? createSphereVolumeStrategy({
|
|
236
|
+
centerPc,
|
|
237
|
+
radiusPc: Number(options.radiusPc),
|
|
238
|
+
})
|
|
239
|
+
: createObserverShellStrategy()
|
|
240
|
+
);
|
|
241
|
+
return {
|
|
242
|
+
id: options.id,
|
|
243
|
+
sessionId: options.sessionId,
|
|
244
|
+
strategy,
|
|
245
|
+
view: {
|
|
246
|
+
observerPc,
|
|
247
|
+
limitingMagnitude,
|
|
248
|
+
...(options.view ?? {}),
|
|
249
|
+
},
|
|
250
|
+
viewRevision: options.viewRevision,
|
|
251
|
+
demandRevision: options.demandRevision,
|
|
252
|
+
attributes: Array.from(options.attributes ?? DEFAULT_STAR_ATTRIBUTES),
|
|
253
|
+
coordinates: options.coordinates,
|
|
254
|
+
streaming: options.streaming,
|
|
255
|
+
memory: options.memory,
|
|
256
|
+
cache: options.cache,
|
|
257
|
+
signal: options.signal,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @param {import('./data.d.ts').SkykitStarDataOptions} options
|
|
263
|
+
*/
|
|
264
|
+
function createProvider(options) {
|
|
265
|
+
if (options.provider) {
|
|
266
|
+
return { provider: options.provider, disposeProvider: false };
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
provider: createStarOctreeProviderService({
|
|
270
|
+
id: options.providerId,
|
|
271
|
+
url: options.octreeUrl ?? OCTREE_DEFAULT,
|
|
272
|
+
persistentCache: options.persistentCache,
|
|
273
|
+
limits: options.limits,
|
|
274
|
+
}),
|
|
275
|
+
disposeProvider: true,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @param {Array<import('@found-in-space/star-trees').StarObjectRef | null | undefined>} refs
|
|
281
|
+
* @param {import('./data.d.ts').SkykitStarLabelOptions & {
|
|
282
|
+
* provider?: import('@found-in-space/star-octree-provider').StarOctreeProviderService;
|
|
283
|
+
* octreeUrl?: string;
|
|
284
|
+
* }} options
|
|
285
|
+
*/
|
|
286
|
+
async function createLabelProvider(refs, options) {
|
|
287
|
+
if (options.metaProvider) {
|
|
288
|
+
return { metaProvider: options.metaProvider, disposeMetaProvider: false };
|
|
289
|
+
}
|
|
290
|
+
const parentDatasetId = options.parentDatasetId
|
|
291
|
+
?? options.datasetId
|
|
292
|
+
?? refs.find((ref) => ref?.datasetId)?.datasetId
|
|
293
|
+
?? await resolveDatasetId(options);
|
|
294
|
+
if (!parentDatasetId) {
|
|
295
|
+
throw new Error('loadStarLabels() needs a dataset id. Pass rows from loadStarRows(), parentDatasetId, or a provider.');
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
metaProvider: createMetaSidecarProviderService({
|
|
299
|
+
id: options.metaProviderId,
|
|
300
|
+
parentDatasetId,
|
|
301
|
+
url: options.metaUrl ?? deriveMetaSidecarUrlFromRenderUrl(options.octreeUrl ?? OCTREE_DEFAULT),
|
|
302
|
+
persistentCache: options.persistentCache,
|
|
303
|
+
limits: options.metaLimits,
|
|
304
|
+
}),
|
|
305
|
+
disposeMetaProvider: true,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* @param {{ provider?: import('@found-in-space/star-octree-provider').StarOctreeProviderService; octreeUrl?: string }} options
|
|
311
|
+
*/
|
|
312
|
+
async function resolveDatasetId(options) {
|
|
313
|
+
if (options.provider) {
|
|
314
|
+
const bootstrap = await options.provider.ensureBootstrap?.();
|
|
315
|
+
return bootstrap?.datasetId ?? options.provider.describe?.().datasetId ?? null;
|
|
316
|
+
}
|
|
317
|
+
const provider = createStarOctreeProviderService({
|
|
318
|
+
url: options.octreeUrl ?? OCTREE_DEFAULT,
|
|
319
|
+
});
|
|
320
|
+
try {
|
|
321
|
+
const bootstrap = await provider.ensureBootstrap();
|
|
322
|
+
return bootstrap.datasetId ?? provider.describe().datasetId ?? null;
|
|
323
|
+
} finally {
|
|
324
|
+
await provider.dispose?.();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @param {import('./data.d.ts').SkykitStarRow[]} rows
|
|
330
|
+
* @param {import('./data.d.ts').SkykitStarDataOptions} options
|
|
331
|
+
*/
|
|
332
|
+
function selectRows(rows, options) {
|
|
333
|
+
const selected = rows.slice();
|
|
334
|
+
const sortBy = options.sortBy === undefined ? 'apparentMagnitude' : options.sortBy;
|
|
335
|
+
if (typeof sortBy === 'function') {
|
|
336
|
+
selected.sort(sortBy);
|
|
337
|
+
} else if (sortBy === 'apparentMagnitude') {
|
|
338
|
+
selected.sort(nullableNumberSort('apparentMagnitude'));
|
|
339
|
+
} else if (sortBy === 'distancePc') {
|
|
340
|
+
selected.sort(nullableNumberSort('distancePc'));
|
|
341
|
+
} else if (sortBy === 'magAbs' || sortBy === 'absoluteMagnitude') {
|
|
342
|
+
selected.sort(nullableNumberSort('absoluteMagnitude'));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const maxStars = positiveInteger(options.maxStars, 0);
|
|
346
|
+
return maxStars > 0 ? selected.slice(0, maxStars) : selected;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @param {'apparentMagnitude' | 'distancePc' | 'absoluteMagnitude'} key
|
|
351
|
+
*/
|
|
352
|
+
function nullableNumberSort(key) {
|
|
353
|
+
/**
|
|
354
|
+
* @param {import('./data.d.ts').SkykitStarRow} left
|
|
355
|
+
* @param {import('./data.d.ts').SkykitStarRow} right
|
|
356
|
+
*/
|
|
357
|
+
return (left, right) => {
|
|
358
|
+
const leftValue = left[key];
|
|
359
|
+
const rightValue = right[key];
|
|
360
|
+
if (leftValue == null && rightValue == null) return 0;
|
|
361
|
+
if (leftValue == null) return 1;
|
|
362
|
+
if (rightValue == null) return -1;
|
|
363
|
+
return leftValue - rightValue;
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* @param {unknown} value
|
|
369
|
+
* @returns {import('@found-in-space/star-trees').StarObjectRef | null}
|
|
370
|
+
*/
|
|
371
|
+
function resolveStarRef(value) {
|
|
372
|
+
if (!value || typeof value !== 'object') return null;
|
|
373
|
+
const candidate = /** @type {{ ref?: unknown; level?: unknown; mortonCode?: unknown; ordinal?: unknown; datasetId?: unknown }} */ (value);
|
|
374
|
+
if (candidate.ref) return resolveStarRef(candidate.ref);
|
|
375
|
+
if (
|
|
376
|
+
Number.isInteger(candidate.level) &&
|
|
377
|
+
typeof candidate.mortonCode === 'string' &&
|
|
378
|
+
Number.isInteger(candidate.ordinal)
|
|
379
|
+
) {
|
|
380
|
+
return {
|
|
381
|
+
datasetId: typeof candidate.datasetId === 'string' ? candidate.datasetId : null,
|
|
382
|
+
level: candidate.level,
|
|
383
|
+
mortonCode: candidate.mortonCode,
|
|
384
|
+
ordinal: candidate.ordinal,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* @param {import('@found-in-space/star-trees').StarObjectRef} ref
|
|
392
|
+
*/
|
|
393
|
+
function formatStarRef(ref) {
|
|
394
|
+
return `${ref.datasetId ?? 'dataset'}:${ref.level}:${ref.mortonCode}:${ref.ordinal}`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* @param {import('./data.d.ts').SkykitStarDataOptions} options
|
|
399
|
+
*/
|
|
400
|
+
function resolveObserverPc(options) {
|
|
401
|
+
return normalizePoint(options.observerPc ?? options.centerPc, DEFAULT_CENTER_PC);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* @param {unknown} point
|
|
406
|
+
* @param {{ x: number; y: number; z: number }} fallback
|
|
407
|
+
*/
|
|
408
|
+
function normalizePoint(point, fallback) {
|
|
409
|
+
if (!point || typeof point !== 'object') return { ...fallback };
|
|
410
|
+
const candidate = /** @type {{ x?: unknown; y?: unknown; z?: unknown }} */ (point);
|
|
411
|
+
return {
|
|
412
|
+
x: finiteNumber(candidate.x, fallback.x),
|
|
413
|
+
y: finiteNumber(candidate.y, fallback.y),
|
|
414
|
+
z: finiteNumber(candidate.z, fallback.z),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function apparentMagnitudeFromAbsolute(magAbs, distancePc) {
|
|
419
|
+
return magAbs + 5 * (Math.log10(Math.max(distancePc, 1e-6)) - 1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function distanceBetween(left, right) {
|
|
423
|
+
return Math.hypot(left.x - right.x, left.y - right.y, left.z - right.z);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function finiteNumber(value, fallback) {
|
|
427
|
+
const number = Number(value);
|
|
428
|
+
return Number.isFinite(number) ? number : fallback;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function positiveNumber(value, fallback) {
|
|
432
|
+
const number = Number(value);
|
|
433
|
+
return Number.isFinite(number) && number > 0 ? number : fallback;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function positiveInteger(value, fallback) {
|
|
437
|
+
const number = Number(value);
|
|
438
|
+
return Number.isInteger(number) && number > 0 ? number : fallback;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* @param {unknown} value
|
|
443
|
+
* @returns {value is Iterable<unknown>}
|
|
444
|
+
*/
|
|
445
|
+
function isIterable(value) {
|
|
446
|
+
return Boolean(value && typeof value === 'object' && Symbol.iterator in value);
|
|
447
|
+
}
|
package/src/embed.d.ts
CHANGED
package/src/embed.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import { parseSpatialLookAtText } from '@found-in-space/spatial';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
installSkykitBrowserGlobal,
|
|
5
|
+
registerBrowserAddon,
|
|
6
|
+
registerBrowserInstance,
|
|
7
|
+
} from './browser-addons.js';
|
|
1
8
|
import { createSkykitBrowser } from './browser.js';
|
|
2
9
|
|
|
3
10
|
const DEFAULT_SELECTOR = '[data-skykit-browser]';
|
|
4
11
|
const started = new WeakSet();
|
|
12
|
+
const skykitGlobal = typeof globalThis !== 'undefined'
|
|
13
|
+
? installSkykitBrowserGlobal(globalThis)
|
|
14
|
+
: null;
|
|
5
15
|
|
|
6
16
|
if (typeof document !== 'undefined') {
|
|
7
17
|
ready(() => {
|
|
@@ -9,6 +19,17 @@ if (typeof document !== 'undefined') {
|
|
|
9
19
|
if (started.has(host)) continue;
|
|
10
20
|
started.add(host);
|
|
11
21
|
void createSkykitBrowser(readOptions(host))
|
|
22
|
+
.then(async (browser) => {
|
|
23
|
+
await installRequestedCapabilities(host, browser);
|
|
24
|
+
if (skykitGlobal) {
|
|
25
|
+
const unregister = registerBrowserInstance(skykitGlobal, host, browser);
|
|
26
|
+
await browser.install({
|
|
27
|
+
id: 'skykit-browser-global-record',
|
|
28
|
+
install: () => unregister,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return browser;
|
|
32
|
+
})
|
|
12
33
|
.then((browser) => {
|
|
13
34
|
reportReady(host, browser);
|
|
14
35
|
})
|
|
@@ -19,15 +40,34 @@ if (typeof document !== 'undefined') {
|
|
|
19
40
|
});
|
|
20
41
|
}
|
|
21
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @param {Element} host
|
|
45
|
+
* @param {import('./browser.d.ts').SkykitBrowser} browser
|
|
46
|
+
*/
|
|
47
|
+
async function installRequestedCapabilities(host, browser) {
|
|
48
|
+
const data = isHtmlElement(host) ? host.dataset : {};
|
|
49
|
+
if (data.skykitConstellations != null || data.skykitConstellationManifest != null) {
|
|
50
|
+
await browser.constellations.load({
|
|
51
|
+
skyculture: data.skykitConstellations,
|
|
52
|
+
manifestUrl: data.skykitConstellationManifest,
|
|
53
|
+
assetBaseUrl: data.skykitConstellationAssets,
|
|
54
|
+
art: data.skykitConstellationArt,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
22
59
|
/** @param {Element} host */
|
|
23
60
|
function readOptions(host) {
|
|
24
|
-
const data = host
|
|
61
|
+
const data = isHtmlElement(host) ? host.dataset : {};
|
|
62
|
+
const lookAt = data.skykitLookAt ? parseSpatialLookAtText(data.skykitLookAt) : null;
|
|
25
63
|
return {
|
|
26
64
|
host,
|
|
27
65
|
...(data.skykitStatus ? { status: data.skykitStatus } : {}),
|
|
28
66
|
...(data.skykitMagnitude ? { limitingMagnitude: Number(data.skykitMagnitude) } : {}),
|
|
29
67
|
...(data.skykitSpeed ? { speedPcPerSec: Number(data.skykitSpeed) } : {}),
|
|
30
68
|
...(data.skykitExposure ? { exposure: Number(data.skykitExposure) } : {}),
|
|
69
|
+
...(data.skykitMouseMode ? { mouseMode: data.skykitMouseMode } : {}),
|
|
70
|
+
...(lookAt ? { view: { lookAt } } : {}),
|
|
31
71
|
};
|
|
32
72
|
}
|
|
33
73
|
|
|
@@ -51,12 +91,17 @@ function reportError(host, error) {
|
|
|
51
91
|
detail: { error },
|
|
52
92
|
bubbles: true,
|
|
53
93
|
}));
|
|
54
|
-
const data = host
|
|
94
|
+
const data = isHtmlElement(host) ? host.dataset : {};
|
|
55
95
|
if (!data.skykitStatus) return;
|
|
56
96
|
const status = document.querySelector(data.skykitStatus);
|
|
57
97
|
if (status) status.textContent = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
58
98
|
}
|
|
59
99
|
|
|
100
|
+
/** @param {Element} host */
|
|
101
|
+
function isHtmlElement(host) {
|
|
102
|
+
return typeof HTMLElement !== 'undefined' && host instanceof HTMLElement;
|
|
103
|
+
}
|
|
104
|
+
|
|
60
105
|
/** @param {() => void} callback */
|
|
61
106
|
function ready(callback) {
|
|
62
107
|
if (document.readyState === 'loading') {
|
|
@@ -67,3 +112,8 @@ function ready(callback) {
|
|
|
67
112
|
}
|
|
68
113
|
|
|
69
114
|
export { createSkykitBrowser } from './browser.js';
|
|
115
|
+
export {
|
|
116
|
+
installSkykitBrowserGlobal,
|
|
117
|
+
registerBrowserAddon,
|
|
118
|
+
registerBrowserInstance,
|
|
119
|
+
} from './browser-addons.js';
|
package/src/hr-diagram.js
CHANGED
|
@@ -79,6 +79,8 @@ export function createSkykitHrDiagramPlugin(options) {
|
|
|
79
79
|
let renderedFrames = 0;
|
|
80
80
|
let publishedFrames = 0;
|
|
81
81
|
let surfaceDirty = true;
|
|
82
|
+
/** @type {import('@found-in-space/touch-os').EmbeddedSurfaceService | null} */
|
|
83
|
+
let lastPublishedSurfaces = null;
|
|
82
84
|
/** @type {string | null} */
|
|
83
85
|
let lastFrameSurfaceViewKey = null;
|
|
84
86
|
/** @type {SkykitViewState | null} */
|
|
@@ -121,9 +123,15 @@ export function createSkykitHrDiagramPlugin(options) {
|
|
|
121
123
|
surfaceDirty = false;
|
|
122
124
|
renderedThisFrame = true;
|
|
123
125
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
const surfaces = resolveTouchOsSurfaces(options.touchOs?.surfaces);
|
|
127
|
+
if (surfaces !== lastPublishedSurfaces && lastPublishedSurfaces) {
|
|
128
|
+
surfaceSource.unpublish(lastPublishedSurfaces);
|
|
129
|
+
lastPublishedSurfaces = null;
|
|
130
|
+
}
|
|
131
|
+
if (renderedThisFrame && surfaces) {
|
|
132
|
+
surfaceSource.publish(surfaces, frame.elapsedSeconds);
|
|
126
133
|
publishedFrames += 1;
|
|
134
|
+
lastPublishedSurfaces = surfaces;
|
|
127
135
|
}
|
|
128
136
|
},
|
|
129
137
|
dispose() {
|
|
@@ -131,8 +139,9 @@ export function createSkykitHrDiagramPlugin(options) {
|
|
|
131
139
|
disposed = true;
|
|
132
140
|
unsubscribeSource?.();
|
|
133
141
|
unregisterDemand?.();
|
|
134
|
-
|
|
135
|
-
|
|
142
|
+
const surfaces = lastPublishedSurfaces ?? resolveTouchOsSurfaces(options.touchOs?.surfaces);
|
|
143
|
+
if (surfaces) {
|
|
144
|
+
surfaceSource.unpublish(surfaces);
|
|
136
145
|
}
|
|
137
146
|
surfaceSource.dispose();
|
|
138
147
|
},
|
|
@@ -340,6 +349,15 @@ function normalizeDemandStrategy(value) {
|
|
|
340
349
|
: null;
|
|
341
350
|
}
|
|
342
351
|
|
|
352
|
+
/**
|
|
353
|
+
* @param {import('./index.d.ts').SkykitHrDiagramTouchOsOptions['surfaces']} surfaces
|
|
354
|
+
* @returns {import('@found-in-space/touch-os').EmbeddedSurfaceService | null}
|
|
355
|
+
*/
|
|
356
|
+
function resolveTouchOsSurfaces(surfaces) {
|
|
357
|
+
const resolved = typeof surfaces === 'function' ? surfaces() : surfaces;
|
|
358
|
+
return resolved ?? null;
|
|
359
|
+
}
|
|
360
|
+
|
|
343
361
|
/** @param {import('@found-in-space/hr-diagram').HrDiagramMode} mode */
|
|
344
362
|
function hrModeHasDemand(mode) {
|
|
345
363
|
return mode === HR_DIAGRAM_MODE_VOLUME;
|
|
@@ -375,8 +393,8 @@ function cloneViewState(view) {
|
|
|
375
393
|
...view,
|
|
376
394
|
observerPc: clonePoint(view.observerPc),
|
|
377
395
|
renderObserverPosition: clonePoint(view.renderObserverPosition),
|
|
396
|
+
...(view.lookAt ? { lookAt: { ...view.lookAt } } : {}),
|
|
378
397
|
...(view.targetPc ? { targetPc: clonePoint(view.targetPc) } : {}),
|
|
379
|
-
...(view.directionIcrs ? { directionIcrs: clonePoint(view.directionIcrs) } : {}),
|
|
380
398
|
...(view.motion
|
|
381
399
|
? {
|
|
382
400
|
motion: {
|