@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
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createSkyGrabPlugin,
|
|
9
9
|
createSkykitAnimationLoop,
|
|
10
10
|
createSkykitDebugBridge,
|
|
11
|
+
createSkykitHrDiagramPlugin,
|
|
11
12
|
createSkykitNavigationPlugin,
|
|
12
13
|
createSkykitStarSourcePlugin,
|
|
13
14
|
createSkykitViewer,
|
|
@@ -15,7 +16,11 @@ import {
|
|
|
15
16
|
createViewAnchoredImageController,
|
|
16
17
|
installSkykitDebugGlobal,
|
|
17
18
|
} from '@found-in-space/skykit';
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
createSkykitSurfaceApp,
|
|
21
|
+
createSkykitTabletRoot,
|
|
22
|
+
createTouchOsPanelPlugin,
|
|
23
|
+
} from '@found-in-space/skykit/touch-os';
|
|
19
24
|
import {
|
|
20
25
|
applySkykitXrDepthRange,
|
|
21
26
|
computeSkykitXrDepthRange,
|
|
@@ -23,60 +28,74 @@ import {
|
|
|
23
28
|
createSkykitXrNavigationPlugin,
|
|
24
29
|
createSkykitXrObserverRig,
|
|
25
30
|
createSkykitXrRaySource,
|
|
31
|
+
createSkykitXrRayVisualPlugin,
|
|
26
32
|
createSkykitXrRig,
|
|
27
33
|
createSkykitXrSessionPlugin,
|
|
28
34
|
createSkykitXrStarPickingPlugin,
|
|
29
35
|
} from '@found-in-space/skykit/xr';
|
|
30
36
|
import {
|
|
37
|
+
createActionCard,
|
|
31
38
|
createButton,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
createTextLabel,
|
|
36
|
-
createToggle,
|
|
37
|
-
createValueReadout,
|
|
39
|
+
createSurfaceShell,
|
|
40
|
+
defineControlsApp,
|
|
41
|
+
defineTouchApp,
|
|
38
42
|
} from '@found-in-space/touch-os';
|
|
39
43
|
import { createXrRayPointerSource } from '@found-in-space/touch-os/hosts/three';
|
|
40
44
|
import {
|
|
41
45
|
OCTREE_DEFAULT,
|
|
42
46
|
createStarOctreeProviderService,
|
|
43
47
|
} from '@found-in-space/star-octree-provider';
|
|
48
|
+
import {
|
|
49
|
+
createMetaSidecarProviderService,
|
|
50
|
+
deriveMetaSidecarUrlFromRenderUrl,
|
|
51
|
+
metaSidecarEntryDisplayFields,
|
|
52
|
+
} from '@found-in-space/meta-sidecar-provider';
|
|
44
53
|
import {
|
|
45
54
|
createThreeStarField,
|
|
46
|
-
|
|
55
|
+
createDefaultThreeStarFieldMaterialProfile,
|
|
47
56
|
} from '@found-in-space/three-star-field';
|
|
48
57
|
|
|
49
58
|
const WESTERN_SKYCULTURE_MANIFEST_URL = 'https://unpkg.com/@found-in-space/stellarium-skycultures-western@0.1.0/dist/manifest.json';
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const DEFAULT_WORLD_SCALE =
|
|
59
|
+
const DATASET_ID_c56103 = 'c56103e6-ad4c-41f9-be06-048b48ec632b';
|
|
60
|
+
const SOL_PC = { x: 0, y: 0, z: 0 };
|
|
61
|
+
const ORION_CENTER_PC = { x: 62.775, y: 602.667, z: -12.713 };
|
|
62
|
+
const DEFAULT_WORLD_SCALE = 1;
|
|
54
63
|
const DEFAULT_LIMITING_MAGNITUDE = 7.5;
|
|
55
64
|
const DEFAULT_EXPOSURE_LOG10 = 5;
|
|
56
65
|
const DEFAULT_EXPOSURE = 10 ** DEFAULT_EXPOSURE_LOG10;
|
|
57
66
|
const XR_CONSTELLATION_RADIUS_PC = 8;
|
|
67
|
+
const XR_PANEL_SURFACE = Object.freeze({ width: 420, height: 560, pixelDensity: 1 });
|
|
68
|
+
const XR_PANEL_THEME = Object.freeze({
|
|
69
|
+
backgroundColor: '#07111e',
|
|
70
|
+
surfaceColor: '#101b2a',
|
|
71
|
+
textColor: '#eef8ff',
|
|
72
|
+
mutedTextColor: '#8aa7b4',
|
|
73
|
+
accentColor: '#35d6c8',
|
|
74
|
+
accentTextColor: '#031416',
|
|
75
|
+
borderColor: 'rgba(120, 210, 220, 0.28)',
|
|
76
|
+
focusColor: '#79ffe8',
|
|
77
|
+
overlayColor: 'rgba(4, 12, 23, 0.65)',
|
|
78
|
+
controlHeight: 28,
|
|
79
|
+
spacing: 5,
|
|
80
|
+
padding: 7,
|
|
81
|
+
radius: 6,
|
|
82
|
+
typography: {
|
|
83
|
+
fontFamily: 'ui-sans-serif',
|
|
84
|
+
fontSize: 11,
|
|
85
|
+
lineHeight: 14,
|
|
86
|
+
fontWeight: 500,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
58
89
|
const XR_DEMO_ACTIONS = Object.freeze({
|
|
59
90
|
goSelected: 'xr-demo:selected.go',
|
|
60
|
-
|
|
61
|
-
home: 'xr-demo:page.home',
|
|
62
|
-
waypoints: 'xr-demo:page.waypoints',
|
|
63
|
-
selected: 'xr-demo:page.selected',
|
|
64
|
-
rendering: 'xr-demo:page.rendering',
|
|
65
|
-
}),
|
|
66
|
-
waypointPrefix: 'xr-demo:waypoint.',
|
|
91
|
+
selectSun: 'xr-demo:selected.sun',
|
|
67
92
|
});
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const WAYPOINTS = Object.freeze([
|
|
75
|
-
{ id: 'sol', label: 'Sol', targetPc: { x: 0, y: 0, z: 0 }, approachPc: 2 },
|
|
76
|
-
{ id: 'proxima', label: 'Proxima', targetPc: PROXIMA_CENTAURI_PC, approachPc: 1.6 },
|
|
77
|
-
{ id: 'sirius', label: 'Sirius', targetPc: SIRIUS_PC, approachPc: 1.6 },
|
|
78
|
-
{ id: 'betelgeuse', label: 'Betelgeuse', targetPc: BETELGEUSE_PC, approachPc: 8 },
|
|
79
|
-
]);
|
|
93
|
+
const XR_TABLET_APP_IDS = Object.freeze({
|
|
94
|
+
target: 'space.found.skykit.xr-free-roam.target',
|
|
95
|
+
rendering: 'space.found.skykit.xr-free-roam.rendering',
|
|
96
|
+
hrDiagram: 'space.found.skykit.xr-free-roam.hr-diagram',
|
|
97
|
+
});
|
|
98
|
+
const HR_SURFACE_SIZE = Object.freeze({ width: 1024, height: 640 });
|
|
80
99
|
|
|
81
100
|
const debug = createSkykitDebugBridge();
|
|
82
101
|
installSkykitDebugGlobal(debug);
|
|
@@ -101,30 +120,44 @@ async function main() {
|
|
|
101
120
|
renderer.xr.enabled = true;
|
|
102
121
|
|
|
103
122
|
const camera = new THREE.PerspectiveCamera(60, 1, 0.02, 2000000);
|
|
104
|
-
const
|
|
105
|
-
xrRig
|
|
123
|
+
const initialOrientation = orientationLookingAt(SOL_PC, ORION_CENTER_PC);
|
|
124
|
+
const xrRig = createSkykitXrRig({
|
|
125
|
+
navigationPose: {
|
|
126
|
+
position: SOL_PC,
|
|
127
|
+
orientation: initialOrientation,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const shipDeck = createShipDeckSlab();
|
|
131
|
+
shipDeck.visible = false;
|
|
132
|
+
xrRig.deckRoot.add(shipDeck);
|
|
106
133
|
|
|
107
|
-
const provider = createStarOctreeProviderService({
|
|
134
|
+
const provider = createStarOctreeProviderService({
|
|
135
|
+
url: OCTREE_DEFAULT,
|
|
136
|
+
datasetId: DATASET_ID_c56103,
|
|
137
|
+
});
|
|
138
|
+
const metaProvider = createMetaSidecarProviderService({
|
|
139
|
+
url: deriveMetaSidecarUrlFromRenderUrl(OCTREE_DEFAULT),
|
|
140
|
+
parentDatasetId: DATASET_ID_c56103,
|
|
141
|
+
});
|
|
108
142
|
const source = createSkykitStarSourcePlugin({ provider });
|
|
109
143
|
const starField = createThreeStarField({
|
|
110
144
|
limitingMagnitude: DEFAULT_LIMITING_MAGNITUDE,
|
|
111
145
|
coordinateUnitsPerParsec: DEFAULT_WORLD_SCALE,
|
|
112
146
|
exposure: DEFAULT_EXPOSURE,
|
|
113
|
-
materialProfile:
|
|
147
|
+
materialProfile: createDefaultThreeStarFieldMaterialProfile({
|
|
114
148
|
limitingMagnitude: DEFAULT_LIMITING_MAGNITUDE,
|
|
115
149
|
coordinateUnitsPerParsec: DEFAULT_WORLD_SCALE,
|
|
116
150
|
exposure: DEFAULT_EXPOSURE,
|
|
117
151
|
}),
|
|
118
152
|
});
|
|
119
153
|
const selectedTarget = createSelectedStarTarget();
|
|
120
|
-
|
|
154
|
+
xrRig.originContentRoot.add(selectedTarget.object3d);
|
|
121
155
|
const rightRaySource = createWorldXrRaySource(
|
|
122
156
|
createSkykitXrRaySource({ kind: 'target-ray', handedness: 'right', length: 2000000 }),
|
|
123
157
|
xrRig.xrOrigin,
|
|
124
158
|
);
|
|
125
159
|
const touchPointerSource = createRightHandTouchPointerSource(rightRaySource);
|
|
126
160
|
const panelState = {
|
|
127
|
-
page: 'home',
|
|
128
161
|
limitingMagnitude: DEFAULT_LIMITING_MAGNITUDE,
|
|
129
162
|
exposureLog10: DEFAULT_EXPOSURE_LOG10,
|
|
130
163
|
worldScaleLog10: Math.log10(DEFAULT_WORLD_SCALE),
|
|
@@ -138,6 +171,28 @@ async function main() {
|
|
|
138
171
|
let latestPanelFrame = null;
|
|
139
172
|
let activeXrHandle = null;
|
|
140
173
|
let artController = null;
|
|
174
|
+
let preflightController = null;
|
|
175
|
+
let touchPanel = null;
|
|
176
|
+
let selectionGeneration = 0;
|
|
177
|
+
const hrDiagram = createSkykitHrDiagramPlugin({
|
|
178
|
+
id: 'xr-free-roam-hr-diagram',
|
|
179
|
+
source,
|
|
180
|
+
mode: 'frustum',
|
|
181
|
+
limitingMagnitude: DEFAULT_LIMITING_MAGNITUDE,
|
|
182
|
+
width: HR_SURFACE_SIZE.width,
|
|
183
|
+
height: HR_SURFACE_SIZE.height,
|
|
184
|
+
touchOs: {
|
|
185
|
+
sourceId: 'xr-free-roam-hr-diagram:surface',
|
|
186
|
+
componentId: 'xr-free-roam-hr-diagram:node',
|
|
187
|
+
surfaces: () => touchPanel?.getRuntime()?.getServices().surfaces,
|
|
188
|
+
width: HR_SURFACE_SIZE.width,
|
|
189
|
+
height: HR_SURFACE_SIZE.height,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
const tabletApps = createXrTabletApps({
|
|
193
|
+
hrDiagram,
|
|
194
|
+
renderTargetReadout: createSelectedTargetReadout,
|
|
195
|
+
});
|
|
141
196
|
|
|
142
197
|
const artPlugin = await createConstellationArtPlugin().catch((error) => {
|
|
143
198
|
debug.recordDiagnostic({
|
|
@@ -149,29 +204,30 @@ async function main() {
|
|
|
149
204
|
return null;
|
|
150
205
|
});
|
|
151
206
|
|
|
152
|
-
|
|
207
|
+
touchPanel = createTouchOsPanelPlugin({
|
|
153
208
|
id: 'xr-free-roam-touch-panel',
|
|
154
209
|
priority: 20,
|
|
155
210
|
driver: 'pose-anchored',
|
|
156
211
|
root: createPanelRoot,
|
|
157
|
-
surfaceMetrics:
|
|
212
|
+
surfaceMetrics: XR_PANEL_SURFACE,
|
|
213
|
+
runtimeOptions: {
|
|
214
|
+
theme: XR_PANEL_THEME,
|
|
215
|
+
longPressDelay: 360,
|
|
216
|
+
},
|
|
158
217
|
pointerSources: [touchPointerSource],
|
|
159
218
|
anchorPose(frame) {
|
|
160
219
|
latestPanelFrame = frame;
|
|
161
220
|
return resolveLeftHandPanelPose(frame, xrRig.xrOrigin);
|
|
162
221
|
},
|
|
163
222
|
driverOptions: {
|
|
164
|
-
panelWidth: 0.
|
|
165
|
-
panelHeight: 0.
|
|
223
|
+
panelWidth: 0.32,
|
|
224
|
+
panelHeight: 0.44,
|
|
166
225
|
offset: { x: 0.04, y: 0.02, z: -0.08 },
|
|
167
226
|
tiltRadians: -0.22,
|
|
168
227
|
transparent: true,
|
|
169
228
|
depthTest: false,
|
|
170
229
|
renderOrder: 50,
|
|
171
230
|
},
|
|
172
|
-
onOutput(output) {
|
|
173
|
-
handlePanelOutput(output);
|
|
174
|
-
},
|
|
175
231
|
});
|
|
176
232
|
|
|
177
233
|
const viewer = await createSkykitViewer({
|
|
@@ -191,9 +247,11 @@ async function main() {
|
|
|
191
247
|
scaleBandedContentRoots: new Map(Object.entries(xrRig.scaleBandedContentRoots)),
|
|
192
248
|
},
|
|
193
249
|
view: {
|
|
250
|
+
observerPc: SOL_PC,
|
|
251
|
+
targetPc: ORION_CENTER_PC,
|
|
194
252
|
limitingMagnitude: DEFAULT_LIMITING_MAGNITUDE,
|
|
195
253
|
coordinateUnitsPerParsec: DEFAULT_WORLD_SCALE,
|
|
196
|
-
|
|
254
|
+
lookAt: { orientationIcrs: initialOrientation },
|
|
197
255
|
},
|
|
198
256
|
plugins: [
|
|
199
257
|
createSkykitXrSessionPlugin({
|
|
@@ -201,6 +259,9 @@ async function main() {
|
|
|
201
259
|
referenceSpaceType: 'local-floor',
|
|
202
260
|
onSessionStarted(handle) {
|
|
203
261
|
activeXrHandle = handle;
|
|
262
|
+
shipDeck.visible = true;
|
|
263
|
+
preflightController?.setSessionStatus('XR session active');
|
|
264
|
+
preflightController?.sync();
|
|
204
265
|
invalidatePanel();
|
|
205
266
|
updateXrDepthRange(handle);
|
|
206
267
|
},
|
|
@@ -208,24 +269,35 @@ async function main() {
|
|
|
208
269
|
createSkykitNavigationPlugin(),
|
|
209
270
|
source,
|
|
210
271
|
createStreamingStarsPlugin({ id: 'xr-stars', source, renderer: starField }),
|
|
272
|
+
hrDiagram,
|
|
211
273
|
...(artPlugin ? [artPlugin] : []),
|
|
274
|
+
createXrFreeRoamFrameSyncPlugin({
|
|
275
|
+
update() {
|
|
276
|
+
selectedTarget.update(camera);
|
|
277
|
+
updateXrDepthRange(activeXrHandle);
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
212
280
|
createKeyboardNavigationPlugin({ speedPcPerSec: 2, rotationSpeedDegPerSec: 55 }),
|
|
213
281
|
createSkyGrabPlugin({ target: host, sensitivityRadiansPerPixel: 0.0007 }),
|
|
214
282
|
touchPanel,
|
|
215
283
|
createSkykitXrNavigationPlugin({ moveSpeedPcPerSec: 4 }),
|
|
284
|
+
createSkykitXrRayVisualPlugin({
|
|
285
|
+
id: 'xr-free-roam-right-ray',
|
|
286
|
+
raySource: rightRaySource,
|
|
287
|
+
blockers: [touchPanel],
|
|
288
|
+
color: 0x78ffe7,
|
|
289
|
+
opacity: 0.82,
|
|
290
|
+
length: 2000000,
|
|
291
|
+
renderOrder: 60,
|
|
292
|
+
}),
|
|
216
293
|
createSkykitXrStarPickingPlugin({
|
|
217
294
|
renderer: starField,
|
|
218
295
|
source,
|
|
219
296
|
raySource: rightRaySource,
|
|
220
297
|
blockers: [touchPanel],
|
|
298
|
+
attributes: ['objectRef', 'pickMeta'],
|
|
221
299
|
onPick(event) {
|
|
222
|
-
|
|
223
|
-
label: event.label,
|
|
224
|
-
position: { ...event.pick.position },
|
|
225
|
-
targetPc: renderPositionToPc(event.pick.position, viewer.getViewState().coordinateUnitsPerParsec),
|
|
226
|
-
};
|
|
227
|
-
selectedTarget.setPosition(event.pick.position);
|
|
228
|
-
invalidatePanel();
|
|
300
|
+
selectPickedStar(event);
|
|
229
301
|
},
|
|
230
302
|
}),
|
|
231
303
|
],
|
|
@@ -238,12 +310,26 @@ async function main() {
|
|
|
238
310
|
});
|
|
239
311
|
viewer.on('xr/session-end', () => {
|
|
240
312
|
activeXrHandle = null;
|
|
313
|
+
shipDeck.visible = false;
|
|
314
|
+
preflightController?.setSessionStatus('Session ended');
|
|
315
|
+
preflightController?.sync();
|
|
241
316
|
invalidatePanel();
|
|
242
317
|
});
|
|
243
318
|
|
|
244
319
|
registerDemoActions(viewer);
|
|
245
|
-
wireDomActions(viewer);
|
|
246
320
|
applyRenderState(viewer, starField, source);
|
|
321
|
+
preflightController = createPreflightController({
|
|
322
|
+
viewer,
|
|
323
|
+
panelState,
|
|
324
|
+
starField,
|
|
325
|
+
source,
|
|
326
|
+
applyRenderState,
|
|
327
|
+
invalidatePanel,
|
|
328
|
+
setConstellationArtEnabled,
|
|
329
|
+
isPresenting: () => activeXrHandle?.presenting === true,
|
|
330
|
+
});
|
|
331
|
+
preflightController.sync();
|
|
332
|
+
void preflightController.refreshXrSupport();
|
|
247
333
|
resize();
|
|
248
334
|
window.addEventListener('resize', resize);
|
|
249
335
|
window.addEventListener('beforeunload', () => {
|
|
@@ -251,6 +337,7 @@ async function main() {
|
|
|
251
337
|
selectedTarget.dispose();
|
|
252
338
|
void viewer.dispose();
|
|
253
339
|
void provider.dispose?.();
|
|
340
|
+
void metaProvider.dispose?.();
|
|
254
341
|
});
|
|
255
342
|
loop.start();
|
|
256
343
|
|
|
@@ -266,10 +353,15 @@ async function main() {
|
|
|
266
353
|
|
|
267
354
|
async function createConstellationArtPlugin() {
|
|
268
355
|
const catalog = await createAnchoredImageCatalog({ manifestUrl: WESTERN_SKYCULTURE_MANIFEST_URL });
|
|
356
|
+
debug.recordDiagnostic({
|
|
357
|
+
level: 'info',
|
|
358
|
+
type: 'xr-free-roam/constellation-art-catalog',
|
|
359
|
+
message: `Constellation art catalog loaded with ${catalog.list().length} entries.`,
|
|
360
|
+
});
|
|
269
361
|
artController = createViewAnchoredImageController({
|
|
270
|
-
strategy: '
|
|
271
|
-
maxAngleDeg:
|
|
272
|
-
hysteresisSeconds: 0
|
|
362
|
+
strategy: 'within-angle',
|
|
363
|
+
maxAngleDeg: 34,
|
|
364
|
+
hysteresisSeconds: 0,
|
|
273
365
|
});
|
|
274
366
|
return createAnchoredImageSkyPlugin({
|
|
275
367
|
id: 'xr-constellation-art',
|
|
@@ -281,156 +373,202 @@ async function main() {
|
|
|
281
373
|
opacity: 0.38,
|
|
282
374
|
fadeInSeconds: 0.25,
|
|
283
375
|
fadeOutSeconds: 0.25,
|
|
284
|
-
|
|
285
|
-
|
|
376
|
+
skipTextureErrors: false,
|
|
377
|
+
onTextureError(event) {
|
|
378
|
+
debug.recordDiagnostic({
|
|
379
|
+
level: 'warn',
|
|
380
|
+
type: 'xr-free-roam/constellation-art-texture-error',
|
|
381
|
+
message: `Constellation art texture failed for ${event.entry.label}.`,
|
|
382
|
+
data: {
|
|
383
|
+
key: event.entry.key,
|
|
384
|
+
imageUrl: event.imageUrl,
|
|
385
|
+
},
|
|
386
|
+
error: event.error,
|
|
387
|
+
});
|
|
388
|
+
},
|
|
286
389
|
});
|
|
287
390
|
}
|
|
288
391
|
|
|
289
392
|
function createPanelRoot() {
|
|
290
393
|
if (cachedPanelRoot && cachedPanelRevision === panelRevision) return cachedPanelRoot;
|
|
291
394
|
cachedPanelRevision = panelRevision;
|
|
292
|
-
cachedPanelRoot =
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
395
|
+
cachedPanelRoot = createSkykitTabletRoot({
|
|
396
|
+
id: 'xr-free-roam-tablet',
|
|
397
|
+
apps: tabletApps,
|
|
398
|
+
appStates: {
|
|
399
|
+
[XR_TABLET_APP_IDS.target]: panelState,
|
|
400
|
+
[XR_TABLET_APP_IDS.rendering]: panelState,
|
|
401
|
+
[XR_TABLET_APP_IDS.hrDiagram]: panelState,
|
|
402
|
+
},
|
|
403
|
+
homeControl: 'button',
|
|
404
|
+
taskSwitcher: 'cards',
|
|
405
|
+
taskCloseControl: 'button',
|
|
406
|
+
launcherLayout: {
|
|
407
|
+
tileWidth: 82,
|
|
408
|
+
tileHeight: 86,
|
|
409
|
+
gap: 9,
|
|
410
|
+
bodyPadding: 9,
|
|
411
|
+
iconMinSize: 36,
|
|
412
|
+
iconMaxSize: 44,
|
|
413
|
+
labelGap: 5,
|
|
414
|
+
},
|
|
415
|
+
onAppEvent: handleTabletAppEvent,
|
|
306
416
|
});
|
|
307
417
|
return cachedPanelRoot;
|
|
308
418
|
}
|
|
309
419
|
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
label: waypoint.label,
|
|
314
|
-
actionId: `${XR_DEMO_ACTIONS.waypointPrefix}${waypoint.id}`,
|
|
315
|
-
}));
|
|
316
|
-
}
|
|
317
|
-
if (panelState.page === 'rendering') {
|
|
420
|
+
function createSelectedTargetReadout(state = panelState) {
|
|
421
|
+
const selected = state.selected;
|
|
422
|
+
if (!selected) {
|
|
318
423
|
return [
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
value: panelState.limitingMagnitude,
|
|
323
|
-
min: 4,
|
|
324
|
-
max: 10,
|
|
325
|
-
step: 0.1,
|
|
326
|
-
valueText: panelState.limitingMagnitude.toFixed(1),
|
|
327
|
-
}),
|
|
328
|
-
createSlider('xr-exposure', {
|
|
329
|
-
label: 'Exposure',
|
|
330
|
-
field: 'exposureLog10',
|
|
331
|
-
value: panelState.exposureLog10,
|
|
332
|
-
min: 3.5,
|
|
333
|
-
max: 5.5,
|
|
334
|
-
step: 0.05,
|
|
335
|
-
valueText: `${Math.round(10 ** panelState.exposureLog10).toLocaleString()}`,
|
|
336
|
-
}),
|
|
337
|
-
createSlider('xr-world-scale', {
|
|
338
|
-
label: 'World scale',
|
|
339
|
-
field: 'worldScaleLog10',
|
|
340
|
-
value: panelState.worldScaleLog10,
|
|
341
|
-
min: -3,
|
|
342
|
-
max: 0,
|
|
343
|
-
step: 0.05,
|
|
344
|
-
valueText: `${(10 ** panelState.worldScaleLog10).toPrecision(2)} u/pc`,
|
|
345
|
-
}),
|
|
346
|
-
createToggle('xr-near-floor', {
|
|
347
|
-
label: 'Near floor',
|
|
348
|
-
field: 'nearFloor',
|
|
349
|
-
value: panelState.nearFloor,
|
|
350
|
-
}),
|
|
351
|
-
createToggle('xr-constellation-art', {
|
|
352
|
-
label: 'Constellation art',
|
|
353
|
-
field: 'constellationArt',
|
|
354
|
-
value: panelState.constellationArt,
|
|
355
|
-
}),
|
|
356
|
-
];
|
|
357
|
-
}
|
|
358
|
-
if (panelState.page === 'selected') {
|
|
359
|
-
return [
|
|
360
|
-
createValueReadout('xr-selected-name', {
|
|
361
|
-
label: 'Target',
|
|
362
|
-
value: panelState.selected?.label ?? 'None',
|
|
363
|
-
}),
|
|
364
|
-
createButton('xr-go-selected', {
|
|
365
|
-
label: 'Go to selected',
|
|
366
|
-
actionId: XR_DEMO_ACTIONS.goSelected,
|
|
367
|
-
disabled: !panelState.selected,
|
|
424
|
+
createActionCard('xr-selected-empty', {
|
|
425
|
+
title: 'Target',
|
|
426
|
+
emptyStateText: 'Pick a star or select Sun',
|
|
368
427
|
}),
|
|
369
428
|
];
|
|
370
429
|
}
|
|
371
430
|
return [
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
label: 'Waypoints',
|
|
378
|
-
actionId: XR_DEMO_ACTIONS.pages.waypoints,
|
|
379
|
-
}),
|
|
380
|
-
createButton('xr-home-selected', {
|
|
381
|
-
label: 'Selected Target',
|
|
382
|
-
actionId: XR_DEMO_ACTIONS.pages.selected,
|
|
383
|
-
disabled: !panelState.selected,
|
|
384
|
-
}),
|
|
385
|
-
createButton('xr-home-rendering', {
|
|
386
|
-
label: 'Rendering',
|
|
387
|
-
actionId: XR_DEMO_ACTIONS.pages.rendering,
|
|
388
|
-
}),
|
|
389
|
-
createTextLabel('xr-home-mode', {
|
|
390
|
-
text: activeXrHandle?.presenting ? 'XR active' : 'Desktop',
|
|
391
|
-
tone: 'muted',
|
|
431
|
+
createActionCard('xr-selected-details', {
|
|
432
|
+
title: selected.label || 'Selected star',
|
|
433
|
+
lines: createSelectedIdentifierLines(selected),
|
|
434
|
+
primaryActionId: XR_DEMO_ACTIONS.goSelected,
|
|
435
|
+
primaryActionLabel: 'Fly to',
|
|
392
436
|
}),
|
|
393
437
|
];
|
|
394
438
|
}
|
|
395
439
|
|
|
396
|
-
function
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
440
|
+
function handleTabletAppEvent(event) {
|
|
441
|
+
if (event.type === 'app-change') {
|
|
442
|
+
if (applyTabletStateChange(event.payload)) {
|
|
443
|
+
applyRenderState(viewer, starField, source);
|
|
444
|
+
preflightController?.sync();
|
|
445
|
+
invalidatePanel();
|
|
446
|
+
}
|
|
401
447
|
return;
|
|
402
448
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
449
|
+
|
|
450
|
+
if (event.type !== 'app-action') return;
|
|
451
|
+
if (event.name === XR_DEMO_ACTIONS.goSelected) {
|
|
452
|
+
if (panelState.selected) {
|
|
453
|
+
goToTarget(viewer, panelState.selected.targetPc, 0.65);
|
|
454
|
+
}
|
|
455
|
+
} else if (event.name === XR_DEMO_ACTIONS.selectSun) {
|
|
456
|
+
selectSunTarget();
|
|
408
457
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function applyTabletStateChange(payload) {
|
|
461
|
+
const field = payload?.field;
|
|
462
|
+
const value = payload?.value;
|
|
463
|
+
if (field === 'limitingMagnitude') {
|
|
464
|
+
panelState.limitingMagnitude = clampNumber(value, 4, 10, panelState.limitingMagnitude);
|
|
465
|
+
return true;
|
|
414
466
|
}
|
|
415
|
-
if (
|
|
416
|
-
panelState.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
467
|
+
if (field === 'exposureLog10') {
|
|
468
|
+
panelState.exposureLog10 = clampNumber(value, 3.5, 5.5, panelState.exposureLog10);
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
if (field === 'worldScaleLog10') {
|
|
472
|
+
panelState.worldScaleLog10 = clampNumber(value, -3, 0, panelState.worldScaleLog10);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
if (field === 'nearFloor') {
|
|
476
|
+
panelState.nearFloor = Boolean(value);
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
if (field === 'constellationArt') {
|
|
480
|
+
setConstellationArtEnabled(Boolean(value));
|
|
481
|
+
return true;
|
|
420
482
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function setConstellationArtEnabled(enabled) {
|
|
487
|
+
panelState.constellationArt = enabled;
|
|
488
|
+
artController?.setSelection?.(enabled ? undefined : () => false);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function selectPickedStar(event) {
|
|
492
|
+
const targetPc = renderPositionToPc(event.pick.position, viewer.getViewState().coordinateUnitsPerParsec);
|
|
493
|
+
const generation = selectTarget({
|
|
494
|
+
label: 'Selected star',
|
|
495
|
+
position: { ...event.pick.position },
|
|
496
|
+
targetPc,
|
|
497
|
+
identifiers: createEmptyIdentifierFields(),
|
|
498
|
+
identifierStatus: 'loading',
|
|
499
|
+
}, event.pick.position);
|
|
500
|
+
void resolveSelectedStarIdentifiers(event.pick, generation);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function selectSunTarget() {
|
|
504
|
+
const generation = selectTarget({
|
|
505
|
+
label: 'Sun',
|
|
506
|
+
position: {
|
|
507
|
+
x: SOL_PC.x * viewer.getViewState().coordinateUnitsPerParsec,
|
|
508
|
+
y: SOL_PC.y * viewer.getViewState().coordinateUnitsPerParsec,
|
|
509
|
+
z: SOL_PC.z * viewer.getViewState().coordinateUnitsPerParsec,
|
|
510
|
+
},
|
|
511
|
+
targetPc: { ...SOL_PC },
|
|
512
|
+
identifiers: {
|
|
513
|
+
...createEmptyIdentifierFields(),
|
|
514
|
+
properName: 'Sol',
|
|
515
|
+
primaryLabel: 'Sun',
|
|
516
|
+
},
|
|
517
|
+
identifierStatus: 'synthetic',
|
|
518
|
+
}, SOL_PC);
|
|
519
|
+
selectionGeneration = generation;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function selectTarget(selection, renderPosition) {
|
|
523
|
+
selectionGeneration += 1;
|
|
524
|
+
panelState.selected = selection;
|
|
525
|
+
selectedTarget.setPosition(renderPosition);
|
|
526
|
+
invalidatePanel();
|
|
527
|
+
return selectionGeneration;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function resolveSelectedStarIdentifiers(pick, generation) {
|
|
531
|
+
const ref = pick.objectRef ?? pick.pickMeta ?? null;
|
|
532
|
+
if (!ref) {
|
|
533
|
+
debug.recordDiagnostic({
|
|
534
|
+
level: 'warn',
|
|
535
|
+
type: 'xr-free-roam/star-pick-missing-sidecar-ref',
|
|
536
|
+
message: 'Selected star did not include sidecar lookup metadata.',
|
|
537
|
+
});
|
|
538
|
+
updateSelectedIdentifiers(generation, null, 'unavailable');
|
|
425
539
|
return;
|
|
426
540
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
const entry = await metaProvider.getMeta(ref);
|
|
544
|
+
updateSelectedIdentifiers(
|
|
545
|
+
generation,
|
|
546
|
+
metaSidecarEntryDisplayFields(entry),
|
|
547
|
+
entry ? 'ready' : 'unavailable',
|
|
548
|
+
);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
debug.recordDiagnostic({
|
|
551
|
+
level: 'warn',
|
|
552
|
+
type: 'xr-free-roam/star-sidecar-lookup-error',
|
|
553
|
+
message: 'Selected star identifiers could not be loaded from the metadata sidecar.',
|
|
554
|
+
error,
|
|
555
|
+
});
|
|
556
|
+
updateSelectedIdentifiers(generation, null, 'error');
|
|
431
557
|
}
|
|
432
558
|
}
|
|
433
559
|
|
|
560
|
+
function updateSelectedIdentifiers(generation, fields, status) {
|
|
561
|
+
if (generation !== selectionGeneration || !panelState.selected) return;
|
|
562
|
+
const identifiers = fields ?? createEmptyIdentifierFields();
|
|
563
|
+
panelState.selected = {
|
|
564
|
+
...panelState.selected,
|
|
565
|
+
label: identifiers.primaryLabel || 'Selected star',
|
|
566
|
+
identifiers,
|
|
567
|
+
identifierStatus: status,
|
|
568
|
+
};
|
|
569
|
+
invalidatePanel();
|
|
570
|
+
}
|
|
571
|
+
|
|
434
572
|
function applyRenderState(activeViewer, activeStarField, activeSource) {
|
|
435
573
|
const worldScale = 10 ** panelState.worldScaleLog10;
|
|
436
574
|
const exposure = 10 ** panelState.exposureLog10;
|
|
@@ -452,25 +590,14 @@ async function main() {
|
|
|
452
590
|
}
|
|
453
591
|
|
|
454
592
|
function registerDemoActions(activeViewer) {
|
|
455
|
-
activeViewer.actions.registerAction(XR_DEMO_ACTIONS.pages.home, () => setPanelPage('home'), { label: 'Panel home' });
|
|
456
|
-
activeViewer.actions.registerAction(XR_DEMO_ACTIONS.pages.waypoints, () => setPanelPage('waypoints'), { label: 'Panel waypoints' });
|
|
457
|
-
activeViewer.actions.registerAction(XR_DEMO_ACTIONS.pages.selected, () => setPanelPage('selected'), { label: 'Panel selected target' });
|
|
458
|
-
activeViewer.actions.registerAction(XR_DEMO_ACTIONS.pages.rendering, () => setPanelPage('rendering'), { label: 'Panel rendering' });
|
|
459
593
|
activeViewer.actions.registerAction(XR_DEMO_ACTIONS.goSelected, () => {
|
|
460
594
|
if (panelState.selected) {
|
|
461
595
|
goToTarget(activeViewer, panelState.selected.targetPc, 0.65);
|
|
462
596
|
}
|
|
463
597
|
}, { label: 'Go to selected star' });
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}, { label: waypoint.label });
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function setPanelPage(page) {
|
|
472
|
-
panelState.page = page;
|
|
473
|
-
invalidatePanel();
|
|
598
|
+
activeViewer.actions.registerAction(XR_DEMO_ACTIONS.selectSun, () => {
|
|
599
|
+
selectSunTarget();
|
|
600
|
+
}, { label: 'Select Sun' });
|
|
474
601
|
}
|
|
475
602
|
|
|
476
603
|
function goToTarget(activeViewer, targetPc, approachPc) {
|
|
@@ -480,7 +607,7 @@ async function main() {
|
|
|
480
607
|
view: {
|
|
481
608
|
observerPc,
|
|
482
609
|
targetPc,
|
|
483
|
-
orientationIcrs: orientationLookingAt(observerPc, targetPc),
|
|
610
|
+
lookAt: { orientationIcrs: orientationLookingAt(observerPc, targetPc) },
|
|
484
611
|
},
|
|
485
612
|
durationSecs: 2.4,
|
|
486
613
|
movement: 'smoothstep',
|
|
@@ -491,9 +618,13 @@ async function main() {
|
|
|
491
618
|
function updateXrDepthRange(handle) {
|
|
492
619
|
if (!handle) return;
|
|
493
620
|
const worldScale = 10 ** panelState.worldScaleLog10;
|
|
621
|
+
const view = viewer.getViewState();
|
|
622
|
+
const visibleBounds = starField.getVisibleBounds({ units: 'parsec' });
|
|
494
623
|
const range = computeSkykitXrDepthRange({
|
|
624
|
+
observer: view.observerPc,
|
|
625
|
+
visibleBounds,
|
|
495
626
|
observerCentricSpheres: [
|
|
496
|
-
{ radiusNavigationUnits:
|
|
627
|
+
{ radiusNavigationUnits: XR_CONSTELLATION_RADIUS_PC * 2 },
|
|
497
628
|
],
|
|
498
629
|
scale: {
|
|
499
630
|
navigationUnits: 'pc',
|
|
@@ -524,15 +655,306 @@ async function main() {
|
|
|
524
655
|
touchPointerSource.getLatestPanelFrame = getLatestPanelFrame;
|
|
525
656
|
}
|
|
526
657
|
|
|
527
|
-
function
|
|
528
|
-
|
|
529
|
-
|
|
658
|
+
function createXrFreeRoamFrameSyncPlugin(options) {
|
|
659
|
+
return {
|
|
660
|
+
id: 'xr-free-roam-frame-sync',
|
|
661
|
+
setup(context) {
|
|
662
|
+
context.addPart({
|
|
663
|
+
id: 'xr-free-roam-frame-sync',
|
|
664
|
+
priority: 80,
|
|
665
|
+
update(frame) {
|
|
666
|
+
options.update?.(frame);
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
},
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function createXrTabletApps(options) {
|
|
674
|
+
return [
|
|
675
|
+
createTargetTabletApp(options),
|
|
676
|
+
createRenderingTabletApp(),
|
|
677
|
+
createSkykitSurfaceApp({
|
|
678
|
+
id: XR_TABLET_APP_IDS.hrDiagram,
|
|
679
|
+
name: 'HR',
|
|
680
|
+
icon: { kind: 'symbol', value: 'HR' },
|
|
681
|
+
node: () => options.hrDiagram.getNode(),
|
|
682
|
+
preferredWindow: {
|
|
683
|
+
width: 420,
|
|
684
|
+
height: 526,
|
|
685
|
+
minWidth: 360,
|
|
686
|
+
minHeight: 300,
|
|
687
|
+
resizable: false,
|
|
688
|
+
},
|
|
689
|
+
backgroundColor: '#050c16',
|
|
690
|
+
emptyLabel: 'HR diagram unavailable',
|
|
691
|
+
}),
|
|
692
|
+
];
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function createTargetTabletApp(options) {
|
|
696
|
+
return defineTouchApp({
|
|
697
|
+
manifest: {
|
|
698
|
+
id: XR_TABLET_APP_IDS.target,
|
|
699
|
+
name: 'Target',
|
|
700
|
+
version: '1.0.0',
|
|
701
|
+
icon: { kind: 'symbol', value: 'TG' },
|
|
702
|
+
preferredWindow: {
|
|
703
|
+
width: 420,
|
|
704
|
+
height: 526,
|
|
705
|
+
minWidth: 320,
|
|
706
|
+
minHeight: 260,
|
|
707
|
+
resizable: false,
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
createApp(ctx) {
|
|
711
|
+
return {
|
|
712
|
+
render(state) {
|
|
713
|
+
return createSurfaceShell('xr-target-app', {
|
|
714
|
+
pointerOpaque: true,
|
|
715
|
+
gap: 6,
|
|
716
|
+
padding: 6,
|
|
717
|
+
bodyGap: 5,
|
|
718
|
+
bodyPadding: 0,
|
|
719
|
+
scrollId: 'xr-target-app-scroll',
|
|
720
|
+
backgroundColor: 'rgba(4, 12, 23, 0.86)',
|
|
721
|
+
children: options.renderTargetReadout(state),
|
|
722
|
+
footer: createButton('xr-selected-sun', {
|
|
723
|
+
label: 'Sun',
|
|
724
|
+
actionId: XR_DEMO_ACTIONS.selectSun,
|
|
725
|
+
}),
|
|
726
|
+
});
|
|
727
|
+
},
|
|
728
|
+
handleOutput(output) {
|
|
729
|
+
emitTabletAppOutput(ctx, output);
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
},
|
|
530
733
|
});
|
|
531
|
-
|
|
532
|
-
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function createRenderingTabletApp() {
|
|
737
|
+
return defineControlsApp({
|
|
738
|
+
id: XR_TABLET_APP_IDS.rendering,
|
|
739
|
+
name: 'Render',
|
|
740
|
+
icon: { kind: 'symbol', value: 'RD' },
|
|
741
|
+
preferredSurface: {
|
|
742
|
+
width: 420,
|
|
743
|
+
height: 526,
|
|
744
|
+
minWidth: 320,
|
|
745
|
+
minHeight: 280,
|
|
746
|
+
resizable: false,
|
|
747
|
+
},
|
|
748
|
+
controls: ({ section, slider, status, toggle }) => [
|
|
749
|
+
section('Stars', [
|
|
750
|
+
slider('Limit', 'limitingMagnitude', {
|
|
751
|
+
min: 4,
|
|
752
|
+
max: 10,
|
|
753
|
+
step: 0.1,
|
|
754
|
+
valueText: (state) => `Mag ${state.limitingMagnitude.toFixed(1)}`,
|
|
755
|
+
}),
|
|
756
|
+
slider('Exposure', 'exposureLog10', {
|
|
757
|
+
min: 3.5,
|
|
758
|
+
max: 5.5,
|
|
759
|
+
step: 0.1,
|
|
760
|
+
valueText: (state) => Math.round(10 ** state.exposureLog10).toLocaleString(),
|
|
761
|
+
}),
|
|
762
|
+
slider('Scale', 'worldScaleLog10', {
|
|
763
|
+
min: -3,
|
|
764
|
+
max: 0,
|
|
765
|
+
step: 0.1,
|
|
766
|
+
valueText: (state) => formatWorldScale(10 ** state.worldScaleLog10),
|
|
767
|
+
}),
|
|
768
|
+
]),
|
|
769
|
+
section('Context', [
|
|
770
|
+
toggle('Nearby glow', 'nearFloor'),
|
|
771
|
+
toggle('Constellation art', 'constellationArt'),
|
|
772
|
+
status('Target', (state) => state.selected?.label ?? 'None', { tone: 'muted' }),
|
|
773
|
+
]),
|
|
774
|
+
],
|
|
533
775
|
});
|
|
534
776
|
}
|
|
535
777
|
|
|
778
|
+
function emitTabletAppOutput(ctx, output) {
|
|
779
|
+
if (output?.type === 'action') {
|
|
780
|
+
ctx.actions.emit({
|
|
781
|
+
type: 'app-action',
|
|
782
|
+
appId: ctx.appId,
|
|
783
|
+
instanceId: ctx.instanceId,
|
|
784
|
+
windowId: ctx.windowId,
|
|
785
|
+
name: output.actionId,
|
|
786
|
+
...(output.payload === undefined ? {} : { payload: output.payload }),
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function createPreflightController(options) {
|
|
792
|
+
const shell = document.querySelector('.xr-free-roam-shell');
|
|
793
|
+
const enterButton = document.querySelector('[data-action="enter-xr"]');
|
|
794
|
+
const exitButton = document.querySelector('[data-action="exit-xr"]');
|
|
795
|
+
const supportValue = document.querySelector('[data-xr-supported]');
|
|
796
|
+
const sessionStatus = document.querySelector('[data-session-status]');
|
|
797
|
+
const settingInputs = Array.from(document.querySelectorAll('[data-setting]'))
|
|
798
|
+
.filter((input) => input instanceof HTMLInputElement);
|
|
799
|
+
let xrSupported = null;
|
|
800
|
+
|
|
801
|
+
for (const input of settingInputs) {
|
|
802
|
+
input.addEventListener('input', () => {
|
|
803
|
+
readSettingInput(input, options.panelState, options.setConstellationArtEnabled);
|
|
804
|
+
syncSettingOutputs(options.panelState);
|
|
805
|
+
});
|
|
806
|
+
input.addEventListener('change', () => {
|
|
807
|
+
readSettingInput(input, options.panelState, options.setConstellationArtEnabled);
|
|
808
|
+
options.applyRenderState(options.viewer, options.starField, options.source);
|
|
809
|
+
options.invalidatePanel();
|
|
810
|
+
sync();
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
enterButton?.addEventListener('click', async () => {
|
|
815
|
+
setSessionStatus('Opening XR session');
|
|
816
|
+
sync();
|
|
817
|
+
const results = await options.viewer.actions.invoke(SKYKIT_ACTIONS.xr.enter, null, {
|
|
818
|
+
source: 'xr-free-roam-dom',
|
|
819
|
+
});
|
|
820
|
+
const rejected = results.find((result) => result.status === 'rejected');
|
|
821
|
+
if (rejected) {
|
|
822
|
+
const reason = rejected.reason instanceof Error ? rejected.reason.message : String(rejected.reason);
|
|
823
|
+
setSessionStatus(reason || 'XR session failed');
|
|
824
|
+
} else {
|
|
825
|
+
setSessionStatus('XR session requested');
|
|
826
|
+
}
|
|
827
|
+
sync();
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
exitButton?.addEventListener('click', async () => {
|
|
831
|
+
setSessionStatus('Ending XR session');
|
|
832
|
+
await options.viewer.actions.invoke(SKYKIT_ACTIONS.xr.exit, null, {
|
|
833
|
+
source: 'xr-free-roam-dom',
|
|
834
|
+
});
|
|
835
|
+
sync();
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
sync,
|
|
840
|
+
setSessionStatus,
|
|
841
|
+
refreshXrSupport,
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
function sync() {
|
|
845
|
+
const presenting = options.isPresenting();
|
|
846
|
+
shell?.classList.toggle('is-presenting', presenting);
|
|
847
|
+
if (enterButton instanceof HTMLButtonElement) {
|
|
848
|
+
enterButton.hidden = presenting;
|
|
849
|
+
enterButton.disabled = xrSupported === false || presenting;
|
|
850
|
+
}
|
|
851
|
+
if (exitButton instanceof HTMLButtonElement) {
|
|
852
|
+
exitButton.hidden = !presenting;
|
|
853
|
+
exitButton.disabled = !presenting;
|
|
854
|
+
}
|
|
855
|
+
syncSettingInputs(options.panelState, settingInputs);
|
|
856
|
+
syncSettingOutputs(options.panelState);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function setSessionStatus(text) {
|
|
860
|
+
if (sessionStatus) {
|
|
861
|
+
sessionStatus.textContent = text;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async function refreshXrSupport() {
|
|
866
|
+
try {
|
|
867
|
+
xrSupported = await globalThis.navigator?.xr?.isSessionSupported?.('immersive-vr') ?? false;
|
|
868
|
+
} catch {
|
|
869
|
+
xrSupported = false;
|
|
870
|
+
}
|
|
871
|
+
if (supportValue) {
|
|
872
|
+
supportValue.textContent = xrSupported ? 'Available' : 'Unavailable';
|
|
873
|
+
}
|
|
874
|
+
sync();
|
|
875
|
+
return xrSupported;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function readSettingInput(input, panelState, setConstellationArtEnabled) {
|
|
880
|
+
const field = input.dataset.setting;
|
|
881
|
+
if (field === 'limitingMagnitude') {
|
|
882
|
+
panelState.limitingMagnitude = clampNumber(input.value, 4, 10, panelState.limitingMagnitude);
|
|
883
|
+
} else if (field === 'exposureLog10') {
|
|
884
|
+
panelState.exposureLog10 = clampNumber(input.value, 3.5, 5.5, panelState.exposureLog10);
|
|
885
|
+
} else if (field === 'worldScaleLog10') {
|
|
886
|
+
panelState.worldScaleLog10 = clampNumber(input.value, -3, 0, panelState.worldScaleLog10);
|
|
887
|
+
} else if (field === 'nearFloor') {
|
|
888
|
+
panelState.nearFloor = input.checked;
|
|
889
|
+
} else if (field === 'constellationArt') {
|
|
890
|
+
setConstellationArtEnabled(input.checked);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function syncSettingInputs(panelState, inputs) {
|
|
895
|
+
for (const input of inputs) {
|
|
896
|
+
const field = input.dataset.setting;
|
|
897
|
+
if (field === 'limitingMagnitude') {
|
|
898
|
+
input.value = String(panelState.limitingMagnitude);
|
|
899
|
+
} else if (field === 'exposureLog10') {
|
|
900
|
+
input.value = String(panelState.exposureLog10);
|
|
901
|
+
} else if (field === 'worldScaleLog10') {
|
|
902
|
+
input.value = String(panelState.worldScaleLog10);
|
|
903
|
+
} else if (field === 'nearFloor') {
|
|
904
|
+
input.checked = panelState.nearFloor;
|
|
905
|
+
} else if (field === 'constellationArt') {
|
|
906
|
+
input.checked = panelState.constellationArt;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function syncSettingOutputs(panelState) {
|
|
912
|
+
for (const output of document.querySelectorAll('[data-setting-value]')) {
|
|
913
|
+
const field = output.dataset.settingValue;
|
|
914
|
+
if (field === 'limitingMagnitude') {
|
|
915
|
+
output.textContent = `Mag ${panelState.limitingMagnitude.toFixed(1)}`;
|
|
916
|
+
} else if (field === 'exposureLog10') {
|
|
917
|
+
output.textContent = Math.round(10 ** panelState.exposureLog10).toLocaleString();
|
|
918
|
+
} else if (field === 'worldScaleLog10') {
|
|
919
|
+
output.textContent = formatWorldScale(10 ** panelState.worldScaleLog10);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function formatWorldScale(value) {
|
|
925
|
+
return `${value.toLocaleString('en-US', { maximumSignificantDigits: 3 })} m/pc`;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function createEmptyIdentifierFields() {
|
|
929
|
+
return {
|
|
930
|
+
properName: '',
|
|
931
|
+
bayer: '',
|
|
932
|
+
hd: '',
|
|
933
|
+
hip: '',
|
|
934
|
+
gaia: '',
|
|
935
|
+
primaryLabel: '',
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function createSelectedIdentifierLines(selected) {
|
|
940
|
+
const fields = selected.identifiers ?? createEmptyIdentifierFields();
|
|
941
|
+
const lines = [
|
|
942
|
+
`Proper: ${fields.properName || '-'}`,
|
|
943
|
+
`Bayer: ${fields.bayer || '-'}`,
|
|
944
|
+
`HD: ${fields.hd || '-'}`,
|
|
945
|
+
`HIP: ${fields.hip || '-'}`,
|
|
946
|
+
`Gaia: ${fields.gaia || '-'}`,
|
|
947
|
+
];
|
|
948
|
+
if (selected.identifierStatus === 'loading') {
|
|
949
|
+
lines.push('Loading identifiers...');
|
|
950
|
+
} else if (selected.identifierStatus === 'error') {
|
|
951
|
+
lines.push('Identifiers unavailable');
|
|
952
|
+
} else if (selected.identifierStatus === 'unavailable') {
|
|
953
|
+
lines.push('No catalog identifiers');
|
|
954
|
+
}
|
|
955
|
+
return lines;
|
|
956
|
+
}
|
|
957
|
+
|
|
536
958
|
function createRightHandTouchPointerSource(raySource) {
|
|
537
959
|
const controls = createSkykitXrControlBindings({
|
|
538
960
|
buttons: {
|
|
@@ -557,10 +979,10 @@ function createRightHandTouchPointerSource(raySource) {
|
|
|
557
979
|
|
|
558
980
|
const select = controls.getButton('select');
|
|
559
981
|
const phase = select.pressedEdge ? 'down' : select.releasedEdge ? 'up' : 'move';
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
982
|
+
return {
|
|
983
|
+
pointerId: 'right-trigger',
|
|
984
|
+
pointerType: 'ray',
|
|
985
|
+
handedness: 'right',
|
|
564
986
|
phase,
|
|
565
987
|
timestamp: skykitFrame.elapsedSeconds * 1000,
|
|
566
988
|
sourceId: 'right-controller',
|
|
@@ -661,20 +1083,12 @@ function createSelectedStarTarget() {
|
|
|
661
1083
|
const context = canvas.getContext('2d');
|
|
662
1084
|
const center = canvas.width / 2;
|
|
663
1085
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
664
|
-
context.strokeStyle = 'rgba(
|
|
665
|
-
context.lineWidth =
|
|
666
|
-
context.
|
|
667
|
-
context.
|
|
668
|
-
context.stroke();
|
|
1086
|
+
context.strokeStyle = 'rgba(84, 255, 172, 0.96)';
|
|
1087
|
+
context.lineWidth = 7;
|
|
1088
|
+
context.shadowColor = 'rgba(84, 255, 172, 0.48)';
|
|
1089
|
+
context.shadowBlur = 10;
|
|
669
1090
|
context.beginPath();
|
|
670
|
-
context.
|
|
671
|
-
context.lineTo(center - 22, center);
|
|
672
|
-
context.moveTo(center + 22, center);
|
|
673
|
-
context.lineTo(center + 52, center);
|
|
674
|
-
context.moveTo(center, center - 52);
|
|
675
|
-
context.lineTo(center, center - 22);
|
|
676
|
-
context.moveTo(center, center + 22);
|
|
677
|
-
context.lineTo(center, center + 52);
|
|
1091
|
+
context.arc(center, center, 42, 0, Math.PI * 2);
|
|
678
1092
|
context.stroke();
|
|
679
1093
|
|
|
680
1094
|
const texture = new THREE.CanvasTexture(canvas);
|
|
@@ -683,13 +1097,15 @@ function createSelectedStarTarget() {
|
|
|
683
1097
|
transparent: true,
|
|
684
1098
|
depthTest: false,
|
|
685
1099
|
depthWrite: false,
|
|
686
|
-
sizeAttenuation:
|
|
1100
|
+
sizeAttenuation: true,
|
|
687
1101
|
});
|
|
688
1102
|
const object3d = new THREE.Sprite(material);
|
|
689
1103
|
object3d.name = 'xr-selected-star-target';
|
|
690
1104
|
object3d.visible = false;
|
|
691
1105
|
object3d.renderOrder = 10_000;
|
|
692
|
-
object3d.scale.set(
|
|
1106
|
+
object3d.scale.set(1, 1, 1);
|
|
1107
|
+
const worldPosition = new THREE.Vector3();
|
|
1108
|
+
const cameraPosition = new THREE.Vector3();
|
|
693
1109
|
|
|
694
1110
|
return {
|
|
695
1111
|
object3d,
|
|
@@ -697,6 +1113,17 @@ function createSelectedStarTarget() {
|
|
|
697
1113
|
object3d.position.set(position.x, position.y, position.z);
|
|
698
1114
|
object3d.visible = true;
|
|
699
1115
|
},
|
|
1116
|
+
clear() {
|
|
1117
|
+
object3d.visible = false;
|
|
1118
|
+
},
|
|
1119
|
+
update(camera) {
|
|
1120
|
+
if (!object3d.visible) return;
|
|
1121
|
+
object3d.getWorldPosition(worldPosition);
|
|
1122
|
+
camera.getWorldPosition(cameraPosition);
|
|
1123
|
+
const distance = Math.max(worldPosition.distanceTo(cameraPosition), 0.001);
|
|
1124
|
+
const diameter = Math.max(distance * 0.032, 0.035);
|
|
1125
|
+
object3d.scale.set(diameter, diameter, 1);
|
|
1126
|
+
},
|
|
700
1127
|
dispose() {
|
|
701
1128
|
object3d.parent?.remove(object3d);
|
|
702
1129
|
material.dispose();
|