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