spacemap 0.1.16 → 0.1.18

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/index.d.ts CHANGED
@@ -1104,6 +1104,12 @@ export declare interface MapEvents {
1104
1104
  loading: (loading: boolean) => void;
1105
1105
  /** Boot progress, 0 to 1, monotonic within one load. */
1106
1106
  progress: (fraction: number) => void;
1107
+ /** The map is at rest. The camera does not move, and the view has every
1108
+ * picture, model, tile and terrain grid that it needs. The event fires once
1109
+ * each time that the map comes to rest after a change. The clock can run. A
1110
+ * covered map is not at rest. The catalogue points and the star background
1111
+ * do not count. */
1112
+ idle: () => void;
1107
1113
  /** The data could not be loaded. The map stays mounted and empty. */
1108
1114
  error: (message: string) => void;
1109
1115
  /** The export was republished while this map was open: its data is now a
@@ -1367,7 +1373,7 @@ export declare interface PanoramaEntry {
1367
1373
  * `unknown` means the sphere sits at an arbitrary azimuth, so nothing
1368
1374
  * may be drawn or written as a heading. The full set `export/panoramas.py`
1369
1375
  * writes — a wider union would erase the literals and stop checking. */
1370
- orientation?: 'unknown' | 'caption-aligned' | 'matched to an archival sphere' | 'matched to a controlled mosaic';
1376
+ orientation?: 'unknown' | 'caption-aligned' | 'matched to an archival sphere' | 'matched to a controlled mosaic' | 'matched to a terrain model' | 'fitted to the Sun';
1371
1377
  /** `estimated` where the sphere's angular bounds were fitted to a
1372
1378
  * published flat image rather than read from an archive label. */
1373
1379
  geometry?: 'estimated';
@@ -1847,6 +1853,8 @@ export declare class SimClock {
1847
1853
  private prevScale;
1848
1854
  private stops;
1849
1855
  private settleTimer;
1856
+ /** The promises of {@link jumpTo} that wait for a frame at their date. */
1857
+ private arrivals;
1850
1858
  constructor(initialJd: number, live?: boolean);
1851
1859
  /* Excluded from this release type: tick */
1852
1860
  /* Excluded from this release type: setBoundaryStops */
@@ -1854,9 +1862,12 @@ export declare class SimClock {
1854
1862
  setTimeScale(magnitude: number): void;
1855
1863
  toggleDirection(): void;
1856
1864
  /* Excluded from this release type: setJD */
1857
- /** Seek, and mark it as a discrete jump so listeners can react to the one
1858
- * move rather than to a stream of them. */
1859
- jumpTo(jd: number): void;
1865
+ /** Move the clock to `jd` in one jump, so that listeners react to it once.
1866
+ * The promise resolves when a frame shows the bodies at that date. Until
1867
+ * the data of the date loads, the map shows the date before the jump, and
1868
+ * the measures answer for that date. */
1869
+ jumpTo(jd: number): Promise<void>;
1870
+ /* Excluded from this release type: placed */
1860
1871
  /** Rest the settled date on where the clock stands now. */
1861
1872
  private settle;
1862
1873
  /** The same, once a drag holds still. A clock still playing is left alone: it
@@ -1871,8 +1882,9 @@ export declare class SimClock {
1871
1882
  get playing(): boolean;
1872
1883
  /** The moment the map is showing. */
1873
1884
  get date(): Date;
1874
- /** Show another moment, as one deliberate move. */
1875
- setDate(date: Date): void;
1885
+ /** Move the clock to `date` in one jump. The promise resolves as the promise
1886
+ * of {@link jumpTo} does. */
1887
+ setDate(date: Date): Promise<void>;
1876
1888
  }
1877
1889
 
1878
1890
  /**
@@ -2012,9 +2024,17 @@ export declare class SpaceMap {
2012
2024
  * the move a click on its label makes. Resolves when the camera comes
2013
2025
  * round. */
2014
2026
  panTo(target: FeatureTarget): Promise<void>;
2015
- /** Put the camera somewhere at once, with no flight: what a page restoring a
2016
- * stored view wants, where a flight from nowhere would read as a lurch. */
2017
- jumpTo(target: JumpTarget): this;
2027
+ /**
2028
+ * Put the camera at a place at once, with no flight. Use it to restore a
2029
+ * stored view. A body that the map has not loaded loads first. The promise
2030
+ * resolves one frame after the camera is in its place. It rejects when the
2031
+ * data of the body does not load. A later move stops this move.
2032
+ */
2033
+ jumpTo(target: JumpTarget): Promise<void>;
2034
+ /** Whether `id` is in the scene, with a place at the date it shows. */
2035
+ private placedNow;
2036
+ /** The placement of {@link jumpTo}, on a body in the scene. */
2037
+ private snap;
2018
2038
  /** Where the camera is now. Null before the first frame, and while nothing
2019
2039
  * is focused. */
2020
2040
  getCamera(): CameraState | null;
package/index.js CHANGED
@@ -110,7 +110,7 @@ var noop = () => {};
110
110
  function run_all(arr) {
111
111
  for (var i = 0; i < arr.length; i++) arr[i]();
112
112
  }
113
- function deferred() {
113
+ function deferred$1() {
114
114
  var resolve;
115
115
  var reject;
116
116
  return {
@@ -902,7 +902,7 @@ var Batch = class Batch {
902
902
  this.#discard_callbacks.add(fn);
903
903
  }
904
904
  settled() {
905
- return (this.#deferred ??= deferred()).promise;
905
+ return (this.#deferred ??= deferred$1()).promise;
906
906
  }
907
907
  static ensure() {
908
908
  if (current_batch === null) {
@@ -10378,6 +10378,30 @@ function applySouthTowardParent(obj, bodyPos, parentPos) {
10378
10378
  function applyUpVector(obj, dir) {
10379
10379
  obj.quaternion.setFromUnitVectors(LOCAL_NORTH, dir);
10380
10380
  }
10381
+ function landedHeadingAt(headings, jd) {
10382
+ let deg;
10383
+ for (const h of headings ?? []) if (h.jd <= jd) deg = h.deg;
10384
+ return deg;
10385
+ }
10386
+ var LOCAL_FRONT = new Vector3(0, 0, 1);
10387
+ var _up = new Vector3();
10388
+ var _front = new Vector3();
10389
+ var _north = new Vector3();
10390
+ var _east = new Vector3();
10391
+ var _target = new Vector3();
10392
+ var _cross = new Vector3();
10393
+ var _turn = new Quaternion();
10394
+ function turnToHeading(obj, north, headingDeg) {
10395
+ _up.copy(LOCAL_NORTH).applyQuaternion(obj.quaternion);
10396
+ _north.copy(north).addScaledVector(_up, -north.dot(_up)).normalize();
10397
+ _east.crossVectors(_north, _up);
10398
+ const h = headingDeg * DEG2RAD$5;
10399
+ _target.copy(_north).multiplyScalar(Math.cos(h)).addScaledVector(_east, Math.sin(h));
10400
+ _front.copy(LOCAL_FRONT).applyQuaternion(obj.quaternion);
10401
+ _front.addScaledVector(_up, -_front.dot(_up)).normalize();
10402
+ const angle = Math.atan2(_cross.crossVectors(_front, _target).dot(_up), _front.dot(_target));
10403
+ obj.quaternion.premultiply(_turn.setFromAxisAngle(_up, angle));
10404
+ }
10381
10405
  var AXIS_VECTORS = {
10382
10406
  "+x": new Vector3(1, 0, 0),
10383
10407
  "-x": new Vector3(-1, 0, 0),
@@ -13517,6 +13541,11 @@ function disposeChain(chain) {
13517
13541
  chain.uniforms.map.value = null;
13518
13542
  chain.uniforms.mask.value = null;
13519
13543
  }
13544
+ function chainSettled(chain, tiles) {
13545
+ const store = chain.store;
13546
+ if (!store || chain.dead || chain.uniforms.window.value[0].w === 0) return true;
13547
+ return store.windows.every((win) => settled(chain, store, tiles, win));
13548
+ }
13520
13549
  function restChain(chain, now) {
13521
13550
  switchOff(chain);
13522
13551
  if (now - chain.lastUsed > IDLE_DISPOSE_S * 1e3) disposeChain(chain);
@@ -15765,6 +15794,7 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
15765
15794
  try {
15766
15795
  const detail = await fetchObjectDetail(bo.body.data.id, false);
15767
15796
  bo.body.pointing = detail.global?.pointing;
15797
+ bo.body.landedHeadings = landedHeadings(detail.global?.events?.items);
15768
15798
  const attitudeManifest = detail.global?.attitude;
15769
15799
  if (attitudeManifest && (bo.modelLoadEpoch ?? 0) === epoch) {
15770
15800
  const probeId = bo.body.data.id.replace(/^probe-/, "");
@@ -15804,8 +15834,9 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
15804
15834
  bo.model = root;
15805
15835
  bo.modelName = slug;
15806
15836
  setHaloLoading(bo, false);
15807
- if (meta.scale_meters) {
15808
- const spanKm = meta.scale_meters / 2e3;
15837
+ const spanM = meta.exports[tier]?.scale_meters;
15838
+ if (spanM) {
15839
+ const spanKm = spanM / 2e3;
15809
15840
  bo.body.data.radiusKm = spanKm * (meta.body_span_ratio ?? 1);
15810
15841
  bo.radiusScene = kmToScene(effectiveRadiusKm(bo.body.data));
15811
15842
  bo.modelUnitOverride = meta.body_span_ratio ? kmToScene(spanKm) : void 0;
@@ -15918,6 +15949,7 @@ function unloadBodyModel(bo) {
15918
15949
  bo.model = null;
15919
15950
  bo.modelName = void 0;
15920
15951
  bo.body.pointing = void 0;
15952
+ bo.body.landedHeadings = void 0;
15921
15953
  bo.body.attitudeTrack = void 0;
15922
15954
  bo.body.modelBaseFrame = void 0;
15923
15955
  if (bo.mesh && restoreSphere) bo.mesh.visible = true;
@@ -15937,6 +15969,17 @@ function setHaloLoading(bo, loading) {
15937
15969
  bo.loadingShown = void 0;
15938
15970
  }
15939
15971
  }
15972
+ function landedHeadings(events) {
15973
+ const out = [];
15974
+ for (const e of events ?? []) {
15975
+ const deg = e.type === "landing" ? e.site?.heading_deg : void 0;
15976
+ if (deg !== void 0) out.push({
15977
+ jd: e.jd,
15978
+ deg
15979
+ });
15980
+ }
15981
+ return out.length ? out : void 0;
15982
+ }
15940
15983
  function withBaseFrame(scene, q) {
15941
15984
  if (!q) return scene;
15942
15985
  const wrapper = new Group();
@@ -18708,6 +18751,7 @@ var SimClock = class {
18708
18751
  prevScale = 1;
18709
18752
  stops = null;
18710
18753
  settleTimer = null;
18754
+ arrivals = [];
18711
18755
  constructor(initialJd, live = false) {
18712
18756
  this.jd = initialJd;
18713
18757
  this.settledJd = initialJd;
@@ -18764,6 +18808,19 @@ var SimClock = class {
18764
18808
  this.setJD(jd);
18765
18809
  this.jumps++;
18766
18810
  this.settle();
18811
+ const jumps = this.jumps;
18812
+ return new Promise((resolve) => this.arrivals.push({
18813
+ jumps,
18814
+ resolve
18815
+ }));
18816
+ }
18817
+ placed(jumps) {
18818
+ if (this.arrivals.length === 0) return;
18819
+ this.arrivals = this.arrivals.filter((arrival) => {
18820
+ if (arrival.jumps > jumps) return true;
18821
+ arrival.resolve();
18822
+ return false;
18823
+ });
18767
18824
  }
18768
18825
  settle() {
18769
18826
  if (this.settleTimer !== null) clearTimeout(this.settleTimer);
@@ -18804,7 +18861,7 @@ var SimClock = class {
18804
18861
  return jdToDate(this.jd);
18805
18862
  }
18806
18863
  setDate(date) {
18807
- this.jumpTo(dateToJD(date));
18864
+ return this.jumpTo(dateToJD(date));
18808
18865
  }
18809
18866
  };
18810
18867
 
@@ -21100,6 +21157,7 @@ var SystemDataLoader = class {
21100
21157
  lastBaryId = null;
21101
21158
  pendingUnloads = /* @__PURE__ */ new Set();
21102
21159
  prefetched = /* @__PURE__ */ new Set();
21160
+ loads = 0;
21103
21161
  constructor(scene, ctx, renderer, textureLoader, bodyObjects, clock, onLoaded) {
21104
21162
  this.scene = scene;
21105
21163
  this.ctx = ctx;
@@ -21145,10 +21203,14 @@ var SystemDataLoader = class {
21145
21203
  return isTopLevelParent(baryId) ? null : baryId;
21146
21204
  }
21147
21205
  load(baryId) {
21206
+ this.loads++;
21148
21207
  loadSystemData(baryId, this.bodyObjects, this.scene, this.textureLoader, this.clock.jd, this.renderer.capabilities.maxTextureSize, this.ctx).then(() => {
21149
21208
  this.onLoaded();
21150
21209
  this.ctx.bodies.orientationVersion++;
21151
- });
21210
+ }).finally(() => this.loads--);
21211
+ }
21212
+ isLoading() {
21213
+ return this.loads > 0;
21152
21214
  }
21153
21215
  drainPendingUnloads() {
21154
21216
  if (this.pendingUnloads.size === 0) return;
@@ -24514,6 +24576,7 @@ var OutOfRangeNotifier = class {
24514
24576
  //#region src/lib/scene/position/landed-probe.ts
24515
24577
  var lastSeat = /* @__PURE__ */ new Map();
24516
24578
  var _upScene = new Vector3();
24579
+ var _northScene = new Vector3();
24517
24580
  function renderLandedProbe(d, probe, landed, jd, where, ctx, bodyObjects) {
24518
24581
  const sample = landedPositionAt(landed, jd, landedOpenEnded(probe));
24519
24582
  if (!sample) return null;
@@ -24543,13 +24606,15 @@ function renderLandedProbe(d, probe, landed, jd, where, ctx, bodyObjects) {
24543
24606
  const quat = bodyQuaternion(landingBody.orientation, jd, landingBody.nutPrec);
24544
24607
  const tmp = new Vector3(bx, by, bz).applyQuaternion(quat);
24545
24608
  const up = eff ? _upScene.set(eff.normal[0], eff.normal[1], eff.normal[2]).applyQuaternion(quat) : null;
24609
+ const north = _northScene.set(-Math.sin(latR) * Math.cos(lngR), Math.cos(latR), Math.sin(latR) * Math.sin(lngR)).applyQuaternion(quat);
24546
24610
  d.parentId = bodyKey;
24547
24611
  return {
24548
24612
  x: bodyWorldPos[0] + kmToScene(tmp.x),
24549
24613
  y: bodyWorldPos[1] + kmToScene(tmp.y),
24550
24614
  z: bodyWorldPos[2] + kmToScene(tmp.z),
24551
24615
  parentPos: bodyWorldPos,
24552
- up
24616
+ up,
24617
+ north
24553
24618
  };
24554
24619
  }
24555
24620
 
@@ -24800,8 +24865,12 @@ var Placer = class {
24800
24865
  const up = landedRender.up;
24801
24866
  if (bo?.mesh) if (up) applyUpVector(bo.mesh, up);
24802
24867
  else applySouthTowardParent(bo.mesh, pos, landedRender.parentPos);
24803
- if (bo?.model) if (up) applyUpVector(bo.model, up);
24804
- else applySouthTowardParent(bo.model, pos, landedRender.parentPos);
24868
+ if (bo?.model) {
24869
+ if (up) applyUpVector(bo.model, up);
24870
+ else applySouthTowardParent(bo.model, pos, landedRender.parentPos);
24871
+ const heading = landedHeadingAt(body.landedHeadings, jd);
24872
+ if (heading !== void 0) turnToHeading(bo.model, landedRender.north, heading);
24873
+ }
24805
24874
  return pos;
24806
24875
  }
24807
24876
  const primary = resolveProbePrimary(located.probe, jd, located.fitCenterNaifId, ctx.chebStore ?? null, (id) => this.at(id, jd) !== null);
@@ -25571,6 +25640,10 @@ function gridGeometry(grid) {
25571
25640
  return geometry;
25572
25641
  }
25573
25642
  var tiledStates = /* @__PURE__ */ new WeakMap();
25643
+ var deferred = /* @__PURE__ */ new WeakSet();
25644
+ function terrainSettled(bo) {
25645
+ return !tiledStates.get(bo)?.abort && !deferred.has(bo);
25646
+ }
25574
25647
  var MAX_BUILD_FAILURES = 2;
25575
25648
  function heightTilesOf(bo) {
25576
25649
  const tiles = bo.displacementMeta?.tiles;
@@ -25625,7 +25698,10 @@ function updateTiledWindow(bo, mesh, tiles, altitude, centerTheta, centerPhi, in
25625
25698
  if (held) {
25626
25699
  const still = drift(centerTheta, centerPhi, held.centerTheta, held.centerPhi);
25627
25700
  if (kept === stepLevel && still < RECENTER_FRAC * held.angRadius) return true;
25628
- if (performance.now() - held.builtAt < REBUILD_MIN_MS) return true;
25701
+ if (performance.now() - held.builtAt < REBUILD_MIN_MS) {
25702
+ deferred.add(bo);
25703
+ return true;
25704
+ }
25629
25705
  }
25630
25706
  const grid = planNestedGrid(centerTheta, centerPhi, altitude, .5 * meta.scale_km / radiusKm, finest, bandCells, stepLevel);
25631
25707
  const thetas = grid.rows.at;
@@ -25687,6 +25763,7 @@ function updateTiledWindow(bo, mesh, tiles, altitude, centerTheta, centerPhi, in
25687
25763
  return !!held;
25688
25764
  }
25689
25765
  function updateTerrainWindow(bo, camera, eligible, invalidate) {
25766
+ deferred.delete(bo);
25690
25767
  const mesh = bo.mesh;
25691
25768
  const tex = bo.displacementMap;
25692
25769
  if (!eligible || !mesh || !tex) {
@@ -25725,7 +25802,10 @@ function updateTerrainWindow(bo, camera, eligible, invalidate) {
25725
25802
  if (w.stepLevel === stepLevel) {
25726
25803
  if (drift(centerTheta, centerPhi, w.centerTheta, w.centerPhi) < RECENTER_FRAC * w.angRadius) return true;
25727
25804
  }
25728
- if (performance.now() - w.builtAt < REBUILD_MIN_MS) return true;
25805
+ if (performance.now() - w.builtAt < REBUILD_MIN_MS) {
25806
+ deferred.add(bo);
25807
+ return true;
25808
+ }
25729
25809
  }
25730
25810
  const thetaLo = Math.max(0, centerTheta - angRadius);
25731
25811
  const thetaHi = Math.min(Math.PI, centerTheta + angRadius);
@@ -25822,6 +25902,15 @@ function desiredDemTier(altitudeRadii, current) {
25822
25902
  if (tierRank(down) < tierRank(current)) return down;
25823
25903
  return current;
25824
25904
  }
25905
+ function bodyLoading(bo) {
25906
+ if (bo.textureLoading || bo.displacementLoading || bo.clouds?.textureLoading) return true;
25907
+ if (bo.appearance?.loading || bo.modelLoadPromise) return true;
25908
+ const surface = bo.tileOverlay;
25909
+ if (surface && bo.surfaceTiles && !chainSettled(surface.chain, bo.surfaceTiles)) return true;
25910
+ const relief = bo.reliefTiles;
25911
+ if (relief && !chainSettled(relief.chain, relief.tiles)) return true;
25912
+ return !terrainSettled(bo);
25913
+ }
25825
25914
  function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, focusedId, jd, appearances, invalidate) {
25826
25915
  appearances.apply(bodyObjects, textureLoader);
25827
25916
  const fovRad = camera.fov * Math.PI / 180;
@@ -25876,6 +25965,13 @@ function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, foc
25876
25965
  }
25877
25966
  }
25878
25967
 
25968
+ //#endregion
25969
+ //#region src/lib/scene/perf/calibration-state.svelte.ts
25970
+ var calibrationUi = proxy({
25971
+ progress: null,
25972
+ bootPending: false
25973
+ });
25974
+
25879
25975
  //#endregion
25880
25976
  //#region src/lib/scene/animation/focus.ts
25881
25977
  var FOCUS_DURATION_MS = FOCUS_ROT_PACING.refMs;
@@ -27262,6 +27358,11 @@ var SceneRenderer = class SceneRenderer {
27262
27358
  lastRenderedFocusKey = "";
27263
27359
  lastRenderedCamPos = new Vector3(NaN, NaN, NaN);
27264
27360
  lastRenderedCamQuat = new Quaternion();
27361
+ restCamPos = new Vector3(NaN, NaN, NaN);
27362
+ restCamQuat = new Quaternion();
27363
+ restFocusKey = "";
27364
+ atRest = false;
27365
+ focusLoads = 0;
27265
27366
  unsubscribeBodiesAdded = null;
27266
27367
  _trailView = {
27267
27368
  camX: 0,
@@ -27691,6 +27792,7 @@ var SceneRenderer = class SceneRenderer {
27691
27792
  this.lastRenderedCamPos.copy(this.camera.position);
27692
27793
  this.lastRenderedCamQuat.copy(this.camera.quaternion);
27693
27794
  this.applyJdUpdate(true);
27795
+ if (!this.dateHeld() && !this.seekAwaitsData) this.clock.placed(this.seenJumps);
27694
27796
  this.refreshTravelFocus();
27695
27797
  this.callbacks.onFrame?.(this.sceneJd, renderedDtMs);
27696
27798
  const controlsSettled = this.holdingCamera ? this.applyHeldCamera() : stepFocusAnimation(this.focus, this.camera, this.controls, () => this.repositionAll(), () => this.pointClouds.rebuildBasis());
@@ -27766,7 +27868,27 @@ var SceneRenderer = class SceneRenderer {
27766
27868
  this.renderModelOverlay();
27767
27869
  this.labelRenderer.render(this.scene, this.camera);
27768
27870
  if (this.haloDebug.active) this.haloDebug.draw(this.bodyObjects, this.camera, this.focus.focusTruePos, this.focusController.current?.data.id, this.renderer.domElement.clientWidth, this.renderer.domElement.clientHeight);
27769
- };
27871
+ this.checkRest(nowMs);
27872
+ };
27873
+ checkRest(nowMs) {
27874
+ const moved = this.cameraMovedFrom(this.restCamPos, this.restCamQuat) || this.focusKey() !== this.restFocusKey;
27875
+ this.restCamPos.copy(this.camera.position);
27876
+ this.restCamQuat.copy(this.camera.quaternion);
27877
+ this.restFocusKey = this.focusKey();
27878
+ if (moved || this.settling(nowMs)) this.atRest = false;
27879
+ else if (!this.atRest) {
27880
+ this.atRest = true;
27881
+ this.callbacks.onIdle?.();
27882
+ }
27883
+ }
27884
+ settling(nowMs) {
27885
+ if (this.covered || this.ctx.loading || calibrationUi.bootPending) return true;
27886
+ if (nowMs - this.focus.focusStartTime < this.focus.focusDurationMs) return true;
27887
+ if (this.jumpJd !== null || this.seekAwaitsData) return true;
27888
+ if (this.focusLoads > 0 || this.systemData.isLoading()) return true;
27889
+ for (const bo of this.bodyObjects.values()) if (bo.group.visible && bodyLoading(bo)) return true;
27890
+ return false;
27891
+ }
27770
27892
  syncSceneSamples(insideShell) {
27771
27893
  const target = this.composer.readBuffer;
27772
27894
  const samples = insideShell ? 0 : Math.min(this.tier.sceneSamples, this.maxSceneSamples);
@@ -28167,11 +28289,14 @@ var SceneRenderer = class SceneRenderer {
28167
28289
  if (nowMs - this.focus.focusStartTime < this.focus.focusDurationMs || this.focusKey() !== this.lastRenderedFocusKey || this.ctx.bodies.minorStreaming || this.pointClouds.hasPendingSceneAdds() || this.systemData.hasPendingUnloads()) return true;
28168
28290
  if (this.covered) return false;
28169
28291
  if (this.frameIndex - this.lastRenderedFrame >= SceneRenderer.IDLE_HEARTBEAT_FRAMES) return true;
28170
- const cam = this.camera;
28171
- if (cam.position.distanceToSquared(this.lastRenderedCamPos) > cam.position.lengthSq() * 1e-10 || 8 * (1 - Math.abs(cam.quaternion.dot(this.lastRenderedCamQuat))) > 1e-8 || this.frameIndex < this.wakeUntilFrame || this.haloDebug.active) return true;
28292
+ if (this.cameraMovedFrom(this.lastRenderedCamPos, this.lastRenderedCamQuat) || this.frameIndex < this.wakeUntilFrame || this.haloDebug.active) return true;
28172
28293
  if (this.clock.jd === this.lastRenderedJd) return false;
28173
28294
  return Math.abs(this.clock.timeScale) > 1 || nowMs - this.lastRenderMs >= SceneRenderer.REALTIME_FRAME_MS;
28174
28295
  }
28296
+ cameraMovedFrom(pos, quat) {
28297
+ const cam = this.camera;
28298
+ return !(cam.position.distanceToSquared(pos) <= cam.position.lengthSq() * 1e-10 && 8 * (1 - Math.abs(cam.quaternion.dot(quat))) <= 1e-8);
28299
+ }
28175
28300
  onControlsEnd = () => {
28176
28301
  this.pendingUrlWrite = true;
28177
28302
  this.focusController.clearPendingInitialView();
@@ -28187,13 +28312,15 @@ var SceneRenderer = class SceneRenderer {
28187
28312
  maybeLoadTexture(body) {
28188
28313
  const bo = this.bodyObjects.get(body.data.id);
28189
28314
  if (!bo) return;
28315
+ this.focusLoads += 2;
28316
+ const done = () => this.focusLoads--;
28190
28317
  loadBodyTexture(bo, this.textureLoader, this.sceneJd, this.scene, this.renderer.capabilities.maxTextureSize, this.ctx).then(() => {
28191
28318
  if (this.focusController.current?.data.id !== body.data.id) return;
28192
28319
  if (body.orientation && bo.mesh) {
28193
28320
  applyOrientation(bo.mesh, body.orientation, this.sceneJd, body.nutPrec);
28194
28321
  this.focusController.reapplyInitialViewIfPending();
28195
28322
  }
28196
- });
28323
+ }).finally(done);
28197
28324
  loadBodyModel(bo, this.modelScene, this.ctx).then(() => {
28198
28325
  if (this.focusController.current?.data.id === bo.body.data.id) this.controls.minDistance = minCameraDistance(bo.body, modelMinRadiusKm(bo));
28199
28326
  if (this.ctx.layers.skipped.has("nomenclature")) return;
@@ -28203,7 +28330,7 @@ var SceneRenderer = class SceneRenderer {
28203
28330
  });
28204
28331
  }).then(() => {
28205
28332
  if (this.selectedFeatureId !== null) setActiveFeatureLabel(bo, this.selectedFeatureId);
28206
- });
28333
+ }).finally(done);
28207
28334
  }
28208
28335
  reapplyBodyLayers() {
28209
28336
  const focused = this.focusController.current;
@@ -28661,7 +28788,8 @@ var SceneRenderer = class SceneRenderer {
28661
28788
  setCovered(covered) {
28662
28789
  if (this.covered === covered) return;
28663
28790
  this.covered = covered;
28664
- if (!covered) this.invalidate();
28791
+ if (covered) this.atRest = false;
28792
+ else this.invalidate();
28665
28793
  }
28666
28794
  isContextLost() {
28667
28795
  return this.renderer.getContext().isContextLost();
@@ -28754,13 +28882,6 @@ function disposeTexture(value) {
28754
28882
  if (value && typeof value === "object" && value.isTexture) value.dispose();
28755
28883
  }
28756
28884
 
28757
- //#endregion
28758
- //#region src/lib/scene/perf/calibration-state.svelte.ts
28759
- var calibrationUi = proxy({
28760
- progress: null,
28761
- bootPending: false
28762
- });
28763
-
28764
28885
  //#endregion
28765
28886
  //#region src/lib/scene/perf/atmosphere-benchmark.ts
28766
28887
  var EARTH_RADIUS_KM = 6371;
@@ -31223,6 +31344,7 @@ var SpaceMap = class {
31223
31344
  clock: /* @__PURE__ */ new Set(),
31224
31345
  loading: /* @__PURE__ */ new Set(),
31225
31346
  progress: /* @__PURE__ */ new Set(),
31347
+ idle: /* @__PURE__ */ new Set(),
31226
31348
  error: /* @__PURE__ */ new Set(),
31227
31349
  datastale: /* @__PURE__ */ new Set(),
31228
31350
  notice: /* @__PURE__ */ new Set(),
@@ -31494,6 +31616,7 @@ var SpaceMap = class {
31494
31616
  dtMs
31495
31617
  });
31496
31618
  },
31619
+ onIdle: () => this.emit("idle"),
31497
31620
  onCameraPosition: (latitude, longitude, zoom) => {
31498
31621
  const body = this.focusedBody?.data.id;
31499
31622
  if (body === void 0) return;
@@ -31728,20 +31851,38 @@ var SpaceMap = class {
31728
31851
  panTo(target) {
31729
31852
  return this.toFeature(this.targetBody(target), target.feature, "pan");
31730
31853
  }
31731
- jumpTo(target) {
31854
+ async jumpTo(target) {
31732
31855
  const body = this.targetBody(target);
31856
+ if (this.placedNow(body) && target.facing === void 0 || this.placedNow(target.facing)) this.navigation++;
31857
+ else {
31858
+ const loading = this.goTo(body, {
31859
+ clock: "keep",
31860
+ camera: "none"
31861
+ });
31862
+ const token = this.navigation;
31863
+ const arrival = await loading;
31864
+ if (arrival.outcome === "failed") throw arrival.error;
31865
+ if (arrival.outcome !== "placed" || token !== this.navigation) return;
31866
+ }
31867
+ this.snap(target, body);
31868
+ return this.settled(0);
31869
+ }
31870
+ placedNow(id) {
31871
+ const body = this.ctx.getBody(id);
31872
+ return !!body && !!this.renderer?.placeOf(body);
31873
+ }
31874
+ snap(target, body) {
31733
31875
  if (target.facing !== void 0) {
31734
31876
  this.snapToBodyFacing(body, target.facing, target.elevationDeg ?? 0, this.framing(target, body));
31735
- return this;
31877
+ return;
31736
31878
  }
31737
31879
  if (target.feature) {
31738
31880
  const { featureId, lat, lon, diameterM } = target.feature;
31739
31881
  this.focusOnFeature(body, featureId, lat, lon, diameterM, null, "snap");
31740
- return this;
31882
+ return;
31741
31883
  }
31742
31884
  const camera = this.getCamera();
31743
31885
  this.snapToBody(body, target.lat ?? camera?.lat ?? 45, target.lon ?? camera?.lon ?? 0, this.framing(target, body));
31744
- return this;
31745
31886
  }
31746
31887
  getCamera() {
31747
31888
  const renderer = this.renderer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacemap",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "The map of the Solar System from spacemap.co, on your website!",
5
5
  "keywords": ["solar system", "spacecraft", "orbits", "map", "three.js"],
6
6
  "homepage": "https://spacemap.co",