@found-in-space/skykit 0.2.0-alpha.20260529 → 0.2.0-alpha.20260531

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 CHANGED
@@ -69,19 +69,28 @@ Optional attributes keep small tweaks HTML-only:
69
69
  data-skykit-magnitude="7"
70
70
  data-skykit-speed="4"
71
71
  data-skykit-exposure="2600"
72
- data-skykit-look-at="ra=4.496h, dec=16.948"
72
+ data-skykit-observer="06h 45m 08.9s, -16d 42m 58s, 2.64pc"
73
+ data-skykit-look-at="05h 35m 17.3s, -05d 23m 28s, 414pc"
74
+ data-skykit-coordinate-origin="solar"
73
75
  data-skykit-mouse-mode="strafe"
74
76
  data-skykit-persistent-cache="off"
75
77
  style="width: 100%; height: 520px; background: #02040b"
76
78
  ></div>
77
79
  ```
78
80
 
81
+ `data-skykit-observer` accepts fixed parsec-space `x,y,z` coordinates or
82
+ RA/Dec/distance text such as `06h 45m 08.9s, -16d 42m 58s, 2.64pc`.
79
83
  `data-skykit-look-at` accepts RA/Dec text such as
80
- `ra=4.496h, dec=16.948`, decimal degrees such as `67.447,16.948`, or a
81
- parsec-space `x,y,z` target for exact generated coordinates. `data-skykit-mouse-mode`
82
- defaults to `grab`; use `look` or `strafe` for the first-person mouse-look
83
- direction, or `none` to disable mouse drag controls. Persistent browser Cache API
84
- storage is enabled by default for octree ranges; set
84
+ `05h 36m 12.81s, −01° 12′ 06.9″`, decimal degrees such as
85
+ `84.053393,-1.201926`, RA/Dec/distance text for a fixed heliocentric target,
86
+ or a parsec-space `x,y,z` target for exact generated coordinates.
87
+ RA/Dec/distance resolves from the solar origin; pure RA/Dec remains a
88
+ directional look. `data-skykit-coordinate-origin="solar"` is accepted as
89
+ clarifying markup, while observer-relative shorthand is not part of this alpha
90
+ embed yet. `data-skykit-mouse-mode` defaults to `grab`; use `look` or
91
+ `strafe` for the first-person mouse-look direction, or `none` to disable mouse
92
+ drag controls. Persistent browser Cache API storage is enabled by default for
93
+ octree ranges; set
85
94
  `data-skykit-persistent-cache="off"` to keep caching session-only.
86
95
 
87
96
  The host dispatches `skykit-browser-ready` with `{ browser, viewer }` in
@@ -120,15 +129,17 @@ For small scripted interactions, use the browser handle:
120
129
  <script type="module">
121
130
  import {
122
131
  SKYKIT_ACTIONS,
132
+ createRaDecLookAt,
123
133
  createSkykitNavigationPlugin,
124
134
  } from 'https://esm.sh/@found-in-space/skykit';
125
135
 
126
136
  const browser = await Skykit.whenReady();
127
137
  await browser.install(createSkykitNavigationPlugin());
138
+ const alnilam = createRaDecLookAt('05h 36m 12.81s', '−01° 12′ 06.9″');
128
139
 
129
140
  document.querySelector('#orion').addEventListener('click', () => {
130
141
  browser.viewer.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
131
- view: { lookAt: 'ra=5.919h, dec=7.407' },
142
+ view: { lookAt: alnilam },
132
143
  movement: { durationSecs: 3 },
133
144
  });
134
145
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@found-in-space/skykit",
3
- "version": "0.2.0-alpha.20260529",
3
+ "version": "0.2.0-alpha.20260531",
4
4
  "description": "Slim composition and teaching layer for Found in Space packages",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -72,7 +72,7 @@
72
72
  "@found-in-space/anchored-image": "0.2.0-alpha.0",
73
73
  "@found-in-space/hr-diagram": "0.2.0-alpha.1",
74
74
  "@found-in-space/meta-sidecar-provider": "0.2.0-alpha.1",
75
- "@found-in-space/spatial": "0.2.0-alpha.20260528",
75
+ "@found-in-space/spatial": "0.2.0-alpha.20260530",
76
76
  "@found-in-space/star-octree-provider": "0.2.0-alpha.1",
77
77
  "@found-in-space/star-trees": "0.2.0-alpha.0",
78
78
  "@found-in-space/three-star-field": "0.2.0-alpha.0"
@@ -137,6 +137,35 @@ test('createSkykitBrowser accepts startup lookAt and mouse look mode', async ()
137
137
  });
138
138
  });
139
139
 
140
+ test('createSkykitBrowser starts from observer and solar RA/Dec distance lookAt', async () => {
141
+ await withFakeWindow(async () => {
142
+ const browser = await createSkykitBrowser({
143
+ host: createHost(),
144
+ status: false,
145
+ renderer: createRenderer(),
146
+ provider: createProvider(),
147
+ starField: createStarField(),
148
+ autoResize: false,
149
+ autoDispose: false,
150
+ autoStart: false,
151
+ lookAt: { raDeg: 90, decDeg: 0, distancePc: 10 },
152
+ view: {
153
+ observerPc: { x: 1, y: 0, z: 0 },
154
+ },
155
+ });
156
+
157
+ const view = browser.viewer.getViewState();
158
+ assert.deepEqual(view.observerPc, { x: 1, y: 0, z: 0 });
159
+ assertVectorApprox(view.targetPc, { x: 0, y: 10, z: 0 });
160
+ assertVectorApprox(
161
+ localVectorFromView(view, { x: 0, y: 0, z: -1 }),
162
+ normalizeVector({ x: -1, y: 10, z: 0 }),
163
+ );
164
+
165
+ await browser.dispose();
166
+ });
167
+ });
168
+
140
169
  test('createSkykitBrowser can disable mouse drag controls', async () => {
141
170
  await withFakeWindow(async () => {
142
171
  const browser = await createSkykitBrowser({
@@ -532,3 +561,26 @@ function createStarField() {
532
561
  },
533
562
  };
534
563
  }
564
+
565
+ function localVectorFromView(view, vector) {
566
+ const q = view.orientationIcrs ?? { x: 0, y: 0, z: 0, w: 1 };
567
+ const result = new THREE.Vector3(vector.x, vector.y, vector.z).applyQuaternion(
568
+ new THREE.Quaternion(q.x, q.y, q.z, q.w),
569
+ );
570
+ return { x: result.x, y: result.y, z: result.z };
571
+ }
572
+
573
+ function assertVectorApprox(actual, expected, epsilon = 1e-9) {
574
+ assert.ok(Math.abs(actual.x - expected.x) < epsilon, `x ${actual.x} !== ${expected.x}`);
575
+ assert.ok(Math.abs(actual.y - expected.y) < epsilon, `y ${actual.y} !== ${expected.y}`);
576
+ assert.ok(Math.abs(actual.z - expected.z) < epsilon, `z ${actual.z} !== ${expected.z}`);
577
+ }
578
+
579
+ function normalizeVector(vector) {
580
+ const length = Math.hypot(vector.x, vector.y, vector.z);
581
+ return {
582
+ x: vector.x / length,
583
+ y: vector.y / length,
584
+ z: vector.z / length,
585
+ };
586
+ }
@@ -7,6 +7,9 @@ import {
7
7
  encodeMorton3D,
8
8
  } from '@found-in-space/star-trees';
9
9
 
10
+ import {
11
+ resolveSpatialTarget,
12
+ } from '@found-in-space/spatial';
10
13
  import {
11
14
  SKYKIT_ACTION_NAMESPACE,
12
15
  SKYKIT_ACTIONS,
@@ -17,6 +20,7 @@ import {
17
20
  createDesktopSkykitObserverRig,
18
21
  createObject3dLayer,
19
22
  createObject3dPlugin,
23
+ createRaDecLookAt,
20
24
  createMouseLookPlugin,
21
25
  createSkyGrabPlugin,
22
26
  createSkykitDefaultKeyboardNavigationBindings,
@@ -34,6 +38,7 @@ import {
34
38
  createStreamingStarLayer,
35
39
  createStreamingStarsPlugin,
36
40
  installSkykitDebugGlobal,
41
+ parseSpatialLookAtText,
37
42
  } from '../index.js';
38
43
 
39
44
  function createHost() {
@@ -313,6 +318,50 @@ test('viewer derives camera orientation from lookAt targets, sky coordinates, an
313
318
  assertVectorApprox(localVectorFromView(view, { x: 0, y: 1, z: 0 }), { x: 0, y: 1, z: 0 });
314
319
  await skyViewer.dispose();
315
320
 
321
+ const alnilamViewer = await createSkykitViewer({
322
+ renderer: createRenderer(),
323
+ view: {
324
+ lookAt: createRaDecLookAt('05h 36m 12.81s', '−01° 12′ 06.9″'),
325
+ },
326
+ });
327
+ view = alnilamViewer.getViewState();
328
+ assertVectorApprox(
329
+ localVectorFromView(view, { x: 0, y: 0, z: -1 }),
330
+ directionFromRaDec(84.053375, -1.2019166666666667),
331
+ );
332
+ await alnilamViewer.dispose();
333
+
334
+ const siriusSpec = parseSpatialLookAtText('06h 45m 08.9s, -16d 42m 58s, 2.64pc');
335
+ const orionSpec = parseSpatialLookAtText('05h 35m 17.3s, -05d 23m 28s, 414pc');
336
+ const siriusPc = resolveSpatialTarget(siriusSpec);
337
+ const orionPc = resolveSpatialTarget(orionSpec);
338
+ assert.ok(siriusPc && orionPc && orionSpec);
339
+ const solarTargetViewer = await createSkykitViewer({
340
+ renderer: createRenderer(),
341
+ view: {
342
+ observerPc: siriusPc,
343
+ lookAt: orionSpec,
344
+ },
345
+ });
346
+ view = solarTargetViewer.getViewState();
347
+ assertVectorApprox(view.observerPc, siriusPc);
348
+ assertVectorApprox(view.targetPc, orionPc);
349
+ assertVectorApprox(
350
+ localVectorFromView(view, { x: 0, y: 0, z: -1 }),
351
+ normalizeVector(subtractVectors(orionPc, siriusPc)),
352
+ );
353
+
354
+ const movedObserver = { x: -8, y: 3, z: 11 };
355
+ solarTargetViewer.requestViewState({ observerPc: movedObserver, lookAt: orionSpec }, 'test-solar-radec');
356
+ solarTargetViewer.update(0);
357
+ view = solarTargetViewer.getViewState();
358
+ assertVectorApprox(view.targetPc, orionPc);
359
+ assertVectorApprox(
360
+ localVectorFromView(view, { x: 0, y: 0, z: -1 }),
361
+ normalizeVector(subtractVectors(orionPc, movedObserver)),
362
+ );
363
+ await solarTargetViewer.dispose();
364
+
316
365
  const starViewer = await createSkykitViewer({
317
366
  renderer: createRenderer(),
318
367
  view: { lookAt: { star: 'hyades' } },
@@ -1527,7 +1576,7 @@ test('navigation transition action restores pose with independent lane durations
1527
1576
  });
1528
1577
  viewer.update(1);
1529
1578
  viewer.update(0);
1530
- assert.deepEqual(viewer.getViewState().observerPc, { x: 20, y: 0, z: 0 });
1579
+ assert.deepEqual(viewer.getViewState().observerPc, { x: 10, y: 0, z: 0 });
1531
1580
  assert.deepEqual(viewer.getViewState().orientationIcrs, orientationAfterExplicitTransition);
1532
1581
 
1533
1582
  await viewer.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
@@ -1539,6 +1588,17 @@ test('navigation transition action restores pose with independent lane durations
1539
1588
  assert.deepEqual(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 });
1540
1589
  assert.deepEqual(viewer.getViewState().orientationIcrs, orientationAfterExplicitTransition);
1541
1590
 
1591
+ await viewer.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
1592
+ lookAt: '05:36:12.81, −01:12:06.9',
1593
+ orientation: { durationSecs: 1 },
1594
+ });
1595
+ viewer.update(1);
1596
+ viewer.update(0);
1597
+ assertVectorApprox(
1598
+ localVectorFromView(viewer.getViewState(), { x: 0, y: 0, z: -1 }),
1599
+ directionFromRaDec(84.053375, -1.2019166666666667),
1600
+ );
1601
+
1542
1602
  await viewer.dispose();
1543
1603
  });
1544
1604
 
@@ -1959,6 +2019,34 @@ function assertVectorApprox(actual, expected, epsilon = 1e-9) {
1959
2019
  assert.ok(Math.abs(actual.z - expected.z) < epsilon, `z ${actual.z} !== ${expected.z}`);
1960
2020
  }
1961
2021
 
2022
+ function subtractVectors(a, b) {
2023
+ return {
2024
+ x: a.x - b.x,
2025
+ y: a.y - b.y,
2026
+ z: a.z - b.z,
2027
+ };
2028
+ }
2029
+
2030
+ function normalizeVector(vector) {
2031
+ const length = Math.hypot(vector.x, vector.y, vector.z);
2032
+ return {
2033
+ x: vector.x / length,
2034
+ y: vector.y / length,
2035
+ z: vector.z / length,
2036
+ };
2037
+ }
2038
+
2039
+ function directionFromRaDec(raDeg, decDeg) {
2040
+ const ra = raDeg * Math.PI / 180;
2041
+ const dec = decDeg * Math.PI / 180;
2042
+ const cosDec = Math.cos(dec);
2043
+ return {
2044
+ x: Math.cos(ra) * cosDec,
2045
+ y: Math.sin(ra) * cosDec,
2046
+ z: Math.sin(dec),
2047
+ };
2048
+ }
2049
+
1962
2050
  function createPointerTarget() {
1963
2051
  const target = createEventTarget();
1964
2052
  target.clientWidth = 800;
package/src/browser.js CHANGED
@@ -6,6 +6,12 @@ import {
6
6
  } from '@found-in-space/star-octree-provider';
7
7
  import { createObserverShellStrategy } from '@found-in-space/star-trees';
8
8
  import { createThreeStarField } from '@found-in-space/three-star-field';
9
+ import {
10
+ createRaDecLookAt,
11
+ parseDeclination,
12
+ parseRightAscension,
13
+ parseSpatialLookAtText,
14
+ } from '@found-in-space/spatial';
9
15
 
10
16
  import { createSkykitAnimationLoop } from './animation-loop.js';
11
17
  import { SKYKIT_ACTIONS, SKYKIT_CONTROLS } from './actions.js';
@@ -181,8 +187,12 @@ export async function createSkykitBrowser(input = {}) {
181
187
  skykit: {
182
188
  SKYKIT_ACTIONS,
183
189
  SKYKIT_CONTROLS,
190
+ createRaDecLookAt,
184
191
  createObject3dPlugin,
185
192
  createSkykitNavigationPlugin,
193
+ parseDeclination,
194
+ parseRightAscension,
195
+ parseSpatialLookAtText,
186
196
  },
187
197
  };
188
198
  }
package/src/embed.js CHANGED
@@ -1,4 +1,7 @@
1
- import { parseSpatialLookAtText } from '@found-in-space/spatial';
1
+ import {
2
+ parseSpatialLookAtText,
3
+ resolveSpatialTarget,
4
+ } from '@found-in-space/spatial';
2
5
 
3
6
  import {
4
7
  installSkykitBrowserGlobal,
@@ -59,7 +62,14 @@ async function installRequestedCapabilities(host, browser) {
59
62
  /** @param {Element} host */
60
63
  function readOptions(host) {
61
64
  const data = isHtmlElement(host) ? host.dataset : {};
65
+ const observerPc = data.skykitObserver ? parseSpatialTargetText(data.skykitObserver) : null;
62
66
  const lookAt = data.skykitLookAt ? parseSpatialLookAtText(data.skykitLookAt) : null;
67
+ const view = observerPc || lookAt
68
+ ? {
69
+ ...(observerPc ? { observerPc } : {}),
70
+ ...(lookAt ? { lookAt } : {}),
71
+ }
72
+ : null;
63
73
  return {
64
74
  host,
65
75
  ...(data.skykitStatus ? { status: data.skykitStatus } : {}),
@@ -68,10 +78,19 @@ function readOptions(host) {
68
78
  ...(data.skykitExposure ? { exposure: Number(data.skykitExposure) } : {}),
69
79
  ...(data.skykitMouseMode ? { mouseMode: data.skykitMouseMode } : {}),
70
80
  ...(data.skykitPersistentCache ? { persistentCache: data.skykitPersistentCache } : {}),
71
- ...(lookAt ? { view: { lookAt } } : {}),
81
+ ...(view ? { view } : {}),
72
82
  };
73
83
  }
74
84
 
85
+ /** @param {string} text */
86
+ function parseSpatialTargetText(text) {
87
+ const targetSpec = parseSpatialLookAtText(text);
88
+ const target = resolveSpatialTarget(targetSpec);
89
+ return target && typeof /** @type {Promise<unknown>} */ (target).then !== 'function'
90
+ ? target
91
+ : null;
92
+ }
93
+
75
94
  /**
76
95
  * @param {Element} host
77
96
  * @param {import('./browser.d.ts').SkykitBrowser} browser
package/src/hr-diagram.js CHANGED
@@ -393,7 +393,9 @@ function cloneViewState(view) {
393
393
  ...view,
394
394
  observerPc: clonePoint(view.observerPc),
395
395
  renderObserverPosition: clonePoint(view.renderObserverPosition),
396
- ...(view.lookAt ? { lookAt: { ...view.lookAt } } : {}),
396
+ ...(view.lookAt
397
+ ? { lookAt: typeof view.lookAt === 'object' ? { ...view.lookAt } : view.lookAt }
398
+ : {}),
397
399
  ...(view.targetPc ? { targetPc: clonePoint(view.targetPc) } : {}),
398
400
  ...(view.motion
399
401
  ? {
package/src/index.d.ts CHANGED
@@ -56,7 +56,9 @@ export interface QuaternionLike {
56
56
  w: number;
57
57
  }
58
58
 
59
- export interface SkykitLookAtInput {
59
+ export type SkykitLookAtInput = string | SkykitLookAtSpecInput;
60
+
61
+ export interface SkykitLookAtSpecInput {
60
62
  targetPc?: Vector3Like | [number, number, number];
61
63
  raDeg?: number;
62
64
  raHours?: number;
@@ -68,6 +70,13 @@ export interface SkykitLookAtInput {
68
70
  [key: string]: unknown;
69
71
  }
70
72
 
73
+ export {
74
+ createRaDecLookAt,
75
+ parseDeclination,
76
+ parseRightAscension,
77
+ parseSpatialLookAtText,
78
+ } from '@found-in-space/spatial';
79
+
71
80
  export interface SkykitObserverMotion {
72
81
  velocityPcPerSec: Vector3Like;
73
82
  speedPcPerSec: number;
package/src/index.js CHANGED
@@ -12,6 +12,12 @@ export {
12
12
  } from './actions.js';
13
13
  export { createSkykitAnimationLoop } from './animation-loop.js';
14
14
  export { createSkykitDebugBridge, installSkykitDebugGlobal } from './debug.js';
15
+ export {
16
+ createRaDecLookAt,
17
+ parseDeclination,
18
+ parseRightAscension,
19
+ parseSpatialLookAtText,
20
+ } from '@found-in-space/spatial';
15
21
  export { createSkykitHrDiagramPlugin } from './hr-diagram.js';
16
22
  export { createObject3dLayer } from './layers.js';
17
23
  export { createDesktopSkykitObserverRig } from './observer-rig.js';
package/src/utils.js CHANGED
@@ -211,6 +211,7 @@ function resolveViewLook(input, observerPc, options = {}) {
211
211
  * @returns {import('./index.d.ts').SkykitLookAtInput | null}
212
212
  */
213
213
  function cloneLookAt(lookAt) {
214
+ if (typeof lookAt === 'string') return lookAt;
214
215
  if (!lookAt || typeof lookAt !== 'object') return null;
215
216
  const source = /** @type {Record<string, unknown>} */ (lookAt);
216
217
  return {