astro-viewer 3.12.0 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/astroviewer.cjs +276 -24
  2. package/dist/astroviewer.cjs.map +1 -1
  3. package/dist/astroviewer.js +276 -24
  4. package/dist/astroviewer.js.map +1 -1
  5. package/dist/astroviewer.min.js +1 -1
  6. package/dist/astroviewer.min.js.map +1 -1
  7. package/lib-esm/AstroSphere.d.ts +21 -0
  8. package/lib-esm/AstroSphere.d.ts.map +1 -1
  9. package/lib-esm/AstroSphere.js +136 -13
  10. package/lib-esm/AstroSphere.js.map +1 -1
  11. package/lib-esm/AstroViewer.d.ts +7 -0
  12. package/lib-esm/AstroViewer.d.ts.map +1 -1
  13. package/lib-esm/AstroViewer.js +18 -3
  14. package/lib-esm/AstroViewer.js.map +1 -1
  15. package/lib-esm/Camera.d.ts +14 -0
  16. package/lib-esm/Camera.d.ts.map +1 -1
  17. package/lib-esm/Camera.js +55 -0
  18. package/lib-esm/Camera.js.map +1 -1
  19. package/lib-esm/model/hips/AllSky.d.ts.map +1 -1
  20. package/lib-esm/model/hips/AllSky.js +4 -2
  21. package/lib-esm/model/hips/AllSky.js.map +1 -1
  22. package/lib-esm/model/hips/AncestorTile.d.ts.map +1 -1
  23. package/lib-esm/model/hips/AncestorTile.js +4 -2
  24. package/lib-esm/model/hips/AncestorTile.js.map +1 -1
  25. package/lib-esm/model/hips/Tile.d.ts.map +1 -1
  26. package/lib-esm/model/hips/Tile.js +4 -2
  27. package/lib-esm/model/hips/Tile.js.map +1 -1
  28. package/lib-esm/model/hips/TileBuffer.d.ts +11 -0
  29. package/lib-esm/model/hips/TileBuffer.d.ts.map +1 -1
  30. package/lib-esm/model/hips/TileBuffer.js +47 -0
  31. package/lib-esm/model/hips/TileBuffer.js.map +1 -1
  32. package/lib-esm/model/hips/VisibleTilesManager.d.ts.map +1 -1
  33. package/lib-esm/model/hips/VisibleTilesManager.js +8 -2
  34. package/lib-esm/model/hips/VisibleTilesManager.js.map +1 -1
  35. package/package.json +1 -1
@@ -3668,6 +3668,7 @@ class AstroSphere {
3668
3668
  lockedNorthSouthDecDeg = null;
3669
3669
  keepCameraNorthUp = true;
3670
3670
  gridLabelContainers;
3671
+ fovAnimationResolve = null;
3671
3672
  constructor(canvas, webgl, options = {}) {
3672
3673
  console.log("[AstroSphere] new instance for canvas", canvas.id);
3673
3674
  // Keep global GL context (as in original JS)
@@ -3912,6 +3913,7 @@ class AstroSphere {
3912
3913
  canvas.setPointerCapture(event.pointerId);
3913
3914
  this.mouseDown = true;
3914
3915
  this._camera.cancelFlyTo();
3916
+ this.cancelFoVAnimation();
3915
3917
  const rect = canvas.getBoundingClientRect();
3916
3918
  this.lastMouseX = event.clientX - rect.left; // locale al canvas
3917
3919
  this.lastMouseY = event.clientY - rect.top; // locale al canvas
@@ -4042,6 +4044,7 @@ class AstroSphere {
4042
4044
  const currentFov = this._healpixGrid.getMinFoV();
4043
4045
  const zoomStep = this.computeZoomStep(currentFov, event.deltaY);
4044
4046
  this._camera.cancelFlyTo();
4047
+ this.cancelFoVAnimation();
4045
4048
  // Apply wheel zoom immediately and discard any queued inertia so reversing
4046
4049
  // direction feels responsive instead of "buffered".
4047
4050
  this.zoomInertia = 0;
@@ -4499,25 +4502,128 @@ class AstroSphere {
4499
4502
  throw new Error(`healpixGrid is ${this.healpixGrid}`);
4500
4503
  return FoVUtils_js_1.FoVUtils.getFoVPolygon(this._camera, this.canvas, this._healpixGrid, this._healpixGrid, this._webgl, this._perspectiveMatrixManager.pMatrix);
4501
4504
  }
4502
- changeFoV(deg) {
4503
- const distance = this._healpixGrid.getFoV().computeDistanceFromAngle(deg);
4504
- this._camera.translate(distance);
4505
+ validateFoV(deg) {
4506
+ if (!Number.isFinite(deg) || deg <= 0 || deg >= 180) {
4507
+ throw new RangeError(`FoV must be > 0 and < 180 degrees. Received ${deg}.`);
4508
+ }
4509
+ if (Global_js_1.default.insideSphere) {
4510
+ throw new Error("FoV navigation is currently supported only outside the sphere.");
4511
+ }
4512
+ }
4513
+ /**
4514
+ * Resolve the radial camera distance that produces the requested minimum FoV.
4515
+ *
4516
+ * The search temporarily samples radial distances, then restores the original
4517
+ * camera distance before returning. Callers can therefore either apply the
4518
+ * result immediately or animate toward it.
4519
+ */
4520
+ findRadialDistanceForFoV(deg) {
4521
+ this.validateFoV(deg);
4522
+ const originalDistance = this._camera.getRadialDistance();
4523
+ const minDistance = 1.000001;
4524
+ const maxDistance = 4.0;
4525
+ const tolerance = Math.max(1e-5, deg * 0.001);
4526
+ const maxIterations = 40;
4527
+ let low = minDistance;
4528
+ let high = maxDistance;
4529
+ let bestDistance = originalDistance;
4530
+ let bestError = Number.POSITIVE_INFINITY;
4531
+ for (let iteration = 0; iteration < maxIterations; iteration += 1) {
4532
+ const candidateDistance = (low + high) / 2;
4533
+ this._camera.setRadialDistance(candidateDistance);
4534
+ this._perspectiveMatrixManager.computePerspectiveMatrix(this.canvas, this._camera, Config_js_1.bootSetup.camera_fov_deg, Config_js_1.bootSetup.camera_near_plane, Global_js_1.default.insideSphere);
4535
+ const candidateFoV = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
4536
+ const measuredFoV = candidateFoV.minFoV;
4537
+ const error = Math.abs(measuredFoV - deg);
4538
+ if (error < bestError) {
4539
+ bestError = error;
4540
+ bestDistance = candidateDistance;
4541
+ }
4542
+ if (error <= tolerance) {
4543
+ break;
4544
+ }
4545
+ if (measuredFoV >= deg || measuredFoV >= 179.999) {
4546
+ high = candidateDistance;
4547
+ }
4548
+ else {
4549
+ low = candidateDistance;
4550
+ }
4551
+ }
4552
+ this._camera.setRadialDistance(originalDistance);
4553
+ this._perspectiveMatrixManager.computePerspectiveMatrix(this.canvas, this._camera, Config_js_1.bootSetup.camera_fov_deg, Config_js_1.bootSetup.camera_near_plane, Global_js_1.default.insideSphere);
4505
4554
  this.fov = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
4506
4555
  this._camera.refreshFoV(this.fov.minFoV);
4556
+ return bestDistance;
4557
+ }
4558
+ finishFoVAnimation() {
4559
+ const resolve = this.fovAnimationResolve;
4560
+ this.fovAnimationResolve = null;
4561
+ resolve?.();
4562
+ }
4563
+ cancelFoVAnimation() {
4564
+ this._camera.cancelRadialAnimation();
4565
+ this.finishFoVAnimation();
4566
+ }
4567
+ /** Set the minimum angular field of view immediately, in degrees. */
4568
+ setFoV(deg) {
4569
+ this.validateFoV(deg);
4570
+ this.cancelFoVAnimation();
4571
+ this._camera.cancelFlyTo();
4572
+ this.zoomInertia = 0;
4573
+ const targetDistance = this.findRadialDistanceForFoV(deg);
4574
+ this._camera.setRadialDistance(targetDistance);
4575
+ this._perspectiveMatrixManager.computePerspectiveMatrix(this.canvas, this._camera, Config_js_1.bootSetup.camera_fov_deg, Config_js_1.bootSetup.camera_near_plane, Global_js_1.default.insideSphere);
4576
+ this.fov = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
4577
+ this._camera.refreshFoV(this.fov.minFoV);
4578
+ this.lastCameraMotionAt = performance.now();
4579
+ this._cameraStatusChanged = true;
4580
+ this.emitCameraChanged("set-fov");
4507
4581
  }
4582
+ async flyToFoV(deg, durationMs = 1200) {
4583
+ this.validateFoV(deg);
4584
+ if (!Number.isFinite(durationMs) || durationMs < 0) {
4585
+ throw new RangeError(`FoV animation duration must be >= 0 ms. Received ${durationMs}.`);
4586
+ }
4587
+ if (durationMs === 0) {
4588
+ this.setFoV(deg);
4589
+ return;
4590
+ }
4591
+ this.cancelFoVAnimation();
4592
+ this.zoomInertia = 0;
4593
+ const targetDistance = this.findRadialDistanceForFoV(deg);
4594
+ this._camera.flyToRadialDistance(targetDistance, durationMs);
4595
+ this.lastCameraMotionAt = performance.now();
4596
+ this._cameraStatusChanged = true;
4597
+ return new Promise((resolve) => {
4598
+ this.fovAnimationResolve = resolve;
4599
+ });
4600
+ }
4601
+ async zoomIn(steps = 1, durationMs = 500) {
4602
+ this.validateZoomSteps(steps);
4603
+ const targetFoV = this.getFoV().minFoV / Math.pow(2, steps);
4604
+ return this.flyToFoV(targetFoV, durationMs);
4605
+ }
4606
+ async zoomOut(steps = 1, durationMs = 500) {
4607
+ this.validateZoomSteps(steps);
4608
+ const targetFoV = this.getFoV().minFoV * Math.pow(2, steps);
4609
+ return this.flyToFoV(targetFoV, durationMs);
4610
+ }
4611
+ validateZoomSteps(steps) {
4612
+ if (!Number.isInteger(steps) || steps < 1) {
4613
+ throw new RangeError(`Zoom steps must be a positive integer. Received ${steps}.`);
4614
+ }
4615
+ }
4616
+ /** @deprecated Use setFoV(). */
4617
+ changeFoV(deg) {
4618
+ this.setFoV(deg);
4619
+ }
4620
+ /** @deprecated Use setFoV(). */
4508
4621
  changeFoV2(deg) {
4509
- const newCameraPos = this._healpixGrid
4510
- .getFoV()
4511
- .computeCameraPositionForFoV(deg);
4512
- this._camera.setCameraPosition(newCameraPos);
4622
+ this.setFoV(deg);
4513
4623
  }
4624
+ /** @deprecated Use setFoV(). */
4514
4625
  changeFoV3(deg) {
4515
- const newPos = this._healpixGrid
4516
- .getFoV()
4517
- .computeCameraPositionForAngularDiameter(deg);
4518
- this._camera.setCameraPosition(newPos);
4519
- // Recompute projection after moving the camera
4520
- this._perspectiveMatrixManager.computePerspectiveMatrix(this.canvas, this._camera, Config_js_1.bootSetup.camera_fov_deg, Config_js_1.bootSetup.camera_near_plane, false);
4626
+ this.setFoV(deg);
4521
4627
  }
4522
4628
  getInsideSphere() {
4523
4629
  return Global_js_1.default.insideSphere;
@@ -4527,6 +4633,7 @@ class AstroSphere {
4527
4633
  this.inertiaX = 0;
4528
4634
  this.inertiaY = 0;
4529
4635
  this.zoomInertia = 0;
4636
+ this.cancelFoVAnimation();
4530
4637
  Global_js_1.default.insideSphere = !Global_js_1.default.insideSphere;
4531
4638
  // console.log(global.insideSphere)
4532
4639
  this._camera.toggleInsideSphere();
@@ -4702,6 +4809,14 @@ class AstroSphere {
4702
4809
  this.updateCentralPoint();
4703
4810
  this._perspectiveMatrixManager.computePerspectiveMatrix(canvas, this._camera, Config_js_1.bootSetup.camera_fov_deg, Config_js_1.bootSetup.camera_near_plane, Global_js_1.default.insideSphere);
4704
4811
  }
4812
+ const fovFlying = this._camera.updateRadialAnimation(now);
4813
+ const fovAnimationFinished = fovFlying && !this._camera.isRadialAnimationActive();
4814
+ if (fovFlying) {
4815
+ this.zoomInertia = 0;
4816
+ this.lastCameraMotionAt = now;
4817
+ this._cameraStatusChanged = true;
4818
+ this._perspectiveMatrixManager.computePerspectiveMatrix(canvas, this._camera, Config_js_1.bootSetup.camera_fov_deg, Config_js_1.bootSetup.camera_near_plane, Global_js_1.default.insideSphere);
4819
+ }
4705
4820
  // Zoom inertia
4706
4821
  if (this.zoomInertia !== 0) {
4707
4822
  if (Math.abs(this.zoomInertia) > 0.0001) {
@@ -4750,6 +4865,9 @@ class AstroSphere {
4750
4865
  this._camera.refreshFoV(this.fov.minFoV);
4751
4866
  this.prevFov = this.fov.minFoV;
4752
4867
  }
4868
+ if (fovAnimationFinished) {
4869
+ this.finishFoVAnimation();
4870
+ }
4753
4871
  // Se la camera è ruotata (anche solo per inerzia), aggiorna punto centrale + emetti cameraChanged
4754
4872
  if (cameraRotated) {
4755
4873
  // Ricalcola il punto centrale
@@ -4791,6 +4909,10 @@ class AstroSphere {
4791
4909
  this._webgl.cullFace(Global_js_1.default.insideSphere ? this._webgl.FRONT : this._webgl.BACK);
4792
4910
  this._webgl.blendFunc(this._webgl.SRC_ALPHA, this._webgl.ONE_MINUS_SRC_ALPHA);
4793
4911
  if (this._activeBaseLayer === "hips" && this._activeHiPS) {
4912
+ // During directional flyTo navigation, keep visibility current but render
4913
+ // only HiPS tiles that are already available. Final-view refinement
4914
+ // resumes automatically on the first frame after the flight.
4915
+ this._healpixGrid.visibleTilesManager.tileBuffer.setTileAcquisitionEnabled(!cameraFlying);
4794
4916
  const maxHiPSOrder = this._activeHiPSLayers.length > 0
4795
4917
  ? Math.max(...this._activeHiPSLayers.map((hips) => hips.maxOrder))
4796
4918
  : this._activeHiPS.maxOrder;
@@ -4808,6 +4930,7 @@ class AstroSphere {
4808
4930
  Math.abs(this.zoomInertia) > 0.0001 ||
4809
4931
  Math.abs(this.inertiaX) > 0.02 ||
4810
4932
  Math.abs(this.inertiaY) > 0.02 ||
4933
+ this._camera.isRadialAnimationActive() ||
4811
4934
  nowForGrid - this.lastCameraMotionAt < 220;
4812
4935
  const skyEntityDrawInput = {
4813
4936
  fovDeg: stableFovDeg,
@@ -5343,14 +5466,29 @@ class AstroViewer {
5343
5466
  getFoVPolygon() {
5344
5467
  return this.astroSphere.getFoVPolygon();
5345
5468
  }
5469
+ setFoV(deg) {
5470
+ this.astroSphere.setFoV(deg);
5471
+ }
5472
+ flyToFoV(deg, durationMs = 1200) {
5473
+ return this.astroSphere.flyToFoV(deg, durationMs);
5474
+ }
5475
+ zoomIn(steps = 1, durationMs = 500) {
5476
+ return this.astroSphere.zoomIn(steps, durationMs);
5477
+ }
5478
+ zoomOut(steps = 1, durationMs = 500) {
5479
+ return this.astroSphere.zoomOut(steps, durationMs);
5480
+ }
5481
+ /** @deprecated Use setFoV(). */
5346
5482
  changeFoV(deg) {
5347
- (this, this.astroSphere.changeFoV(deg));
5483
+ this.setFoV(deg);
5348
5484
  }
5485
+ /** @deprecated Use setFoV(). */
5349
5486
  changeFoV2(deg) {
5350
- (this, this.astroSphere.changeFoV2(deg));
5487
+ this.setFoV(deg);
5351
5488
  }
5489
+ /** @deprecated Use setFoV(). */
5352
5490
  changeFoV3(deg) {
5353
- (this, this.astroSphere.changeFoV3(deg));
5491
+ this.setFoV(deg);
5354
5492
  }
5355
5493
  getInsideSphere() {
5356
5494
  return this.astroSphere.getInsideSphere();
@@ -5603,6 +5741,7 @@ class Camera {
5603
5741
  lockRotY = false;
5604
5742
  lockRotZ = false;
5605
5743
  flyToAnimation = null;
5744
+ radialAnimation = null;
5606
5745
  constructor(in_position, in_sphere) {
5607
5746
  this.init(in_position, in_sphere);
5608
5747
  }
@@ -5737,6 +5876,42 @@ class Camera {
5737
5876
  cancelFlyTo() {
5738
5877
  this.flyToAnimation = null;
5739
5878
  }
5879
+ flyToRadialDistance(distance, durationMs = 1200) {
5880
+ if (!Number.isFinite(distance) || distance <= 1) {
5881
+ throw new RangeError(`Camera radial distance must be > 1. Received ${distance}.`);
5882
+ }
5883
+ this.radialAnimation = {
5884
+ startDistance: this.cam_pos[2],
5885
+ targetDistance: distance,
5886
+ startTime: performance.now(),
5887
+ durationMs: Math.max(1, durationMs),
5888
+ };
5889
+ }
5890
+ updateRadialAnimation(now) {
5891
+ const animation = this.radialAnimation;
5892
+ if (!animation) {
5893
+ return false;
5894
+ }
5895
+ const elapsed = now - animation.startTime;
5896
+ const progress = Math.min(1, elapsed / animation.durationMs);
5897
+ const easedProgress = progress < 0.5
5898
+ ? 4 * progress * progress * progress
5899
+ : 1 - Math.pow(-2 * progress + 2, 3) / 2;
5900
+ const distance = animation.startDistance +
5901
+ (animation.targetDistance - animation.startDistance) * easedProgress;
5902
+ this.setRadialDistance(distance);
5903
+ if (progress >= 1) {
5904
+ this.setRadialDistance(animation.targetDistance);
5905
+ this.radialAnimation = null;
5906
+ }
5907
+ return true;
5908
+ }
5909
+ isRadialAnimationActive() {
5910
+ return this.radialAnimation !== null;
5911
+ }
5912
+ cancelRadialAnimation() {
5913
+ this.radialAnimation = null;
5914
+ }
5740
5915
  toggleInsideSphere() {
5741
5916
  // if (inside !== global.insideSphere) {
5742
5917
  // global.insideSphere = inside;
@@ -5986,6 +6161,24 @@ class Camera {
5986
6161
  // Recompute view: vMatrix = inv(T) * inv(R)
5987
6162
  this.refreshViewMatrix();
5988
6163
  }
6164
+ /**
6165
+ * Set the radial camera distance while preserving the current pointing.
6166
+ *
6167
+ * Outside-sphere navigation stores the camera radius in cam_pos[2];
6168
+ * changing only that component is the same radial motion used by zoom(),
6169
+ * without changing the current rotation matrix.
6170
+ */
6171
+ setRadialDistance(distance) {
6172
+ if (!Number.isFinite(distance) || distance <= 1) {
6173
+ throw new RangeError(`Camera radial distance must be > 1. Received ${distance}.`);
6174
+ }
6175
+ this.cam_pos[2] = distance;
6176
+ gl_matrix_1.mat4.translate(this.T, gl_matrix_1.mat4.create(), this.cam_pos);
6177
+ this.refreshViewMatrix();
6178
+ }
6179
+ getRadialDistance() {
6180
+ return this.cam_pos[2];
6181
+ }
5989
6182
  getCameraAngle() {
5990
6183
  const [x, y, z] = this.getCameraPosition();
5991
6184
  const posVec = gl_matrix_1.vec3.fromValues(x, y, z);
@@ -13003,8 +13196,10 @@ class AllSky {
13003
13196
  continue;
13004
13197
  }
13005
13198
  const childTile = this._isGalacticHips
13006
- ? this._tileBuffer.getGalTile(tileno, childrenOrder, this._hips)
13007
- : this._tileBuffer.getTile(tileno, childrenOrder, this._hips);
13199
+ ? this._tileBuffer.getRenderableGalTile(tileno, childrenOrder, this._hips)
13200
+ : this._tileBuffer.getRenderableTile(tileno, childrenOrder, this._hips);
13201
+ if (!childTile)
13202
+ continue;
13008
13203
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, this._hipsShaderProgram);
13009
13204
  if (childTile.getReadyState()) {
13010
13205
  allSkyTiles2Skip.push(tileno);
@@ -13293,8 +13488,10 @@ class AncestorTile {
13293
13488
  continue;
13294
13489
  }
13295
13490
  const childTile = this._isGalacticHips
13296
- ? this._tileBuffer.getGalTile(childTileNo, childrenOrder, this._hips)
13297
- : this._tileBuffer.getTile(childTileNo, childrenOrder, this._hips);
13491
+ ? this._tileBuffer.getRenderableGalTile(childTileNo, childrenOrder, this._hips)
13492
+ : this._tileBuffer.getRenderableTile(childTileNo, childrenOrder, this._hips);
13493
+ if (!childTile)
13494
+ continue;
13298
13495
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, this._hipsShaderProgram);
13299
13496
  if (childTile.getReadyState()) {
13300
13497
  quadrantsToDraw.delete(c);
@@ -14818,8 +15015,10 @@ class Tile {
14818
15015
  continue;
14819
15016
  }
14820
15017
  const childTile = this._isGalacticHips
14821
- ? this._tileBuffer.getGalTile(childTileNo, childrenOrder, this._hips)
14822
- : this._tileBuffer.getTile(childTileNo, childrenOrder, this._hips);
15018
+ ? this._tileBuffer.getRenderableGalTile(childTileNo, childrenOrder, this._hips)
15019
+ : this._tileBuffer.getRenderableTile(childTileNo, childrenOrder, this._hips);
15020
+ if (!childTile)
15021
+ continue;
14823
15022
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, hipsShaderProgram);
14824
15023
  if (childTile.getReadyState()) {
14825
15024
  quadrantsToDraw.delete(c);
@@ -14871,6 +15070,7 @@ class TileBuffer {
14871
15070
  _webgl;
14872
15071
  _visibleTileManager;
14873
15072
  _hipsShaderProgram;
15073
+ _tileAcquisitionEnabled = true;
14874
15074
  constructor(minutesToLiveInCache = 1, webgl, hipsShaderProgram, visibleTileManager) {
14875
15075
  this._hipsShaderProgram = hipsShaderProgram;
14876
15076
  this._visibleTileManager = visibleTileManager;
@@ -14902,8 +15102,14 @@ class TileBuffer {
14902
15102
  }
14903
15103
  this._galActiveHiPS.set(hips, new Map());
14904
15104
  }
15105
+ /** Enable or suspend creation of missing HiPS tiles. */
15106
+ setTileAcquisitionEnabled(enabled) {
15107
+ this._tileAcquisitionEnabled = enabled;
15108
+ }
14905
15109
  /** Preload/add tile for every registered equatorial HiPS. */
14906
15110
  addTile(order, tileno) {
15111
+ if (!this._tileAcquisitionEnabled)
15112
+ return;
14907
15113
  for (const hips of this._activeHiPS.keys()) {
14908
15114
  if (order > hips.maxOrder) {
14909
15115
  continue;
@@ -14916,6 +15122,8 @@ class TileBuffer {
14916
15122
  }
14917
15123
  /** Preload/add tile for every registered galactic HiPS. */
14918
15124
  addGalTile(order, tileno) {
15125
+ if (!this._tileAcquisitionEnabled)
15126
+ return;
14919
15127
  for (const hips of this._galActiveHiPS.keys()) {
14920
15128
  if (order > hips.maxOrder) {
14921
15129
  continue;
@@ -14962,6 +15170,44 @@ class TileBuffer {
14962
15170
  }
14963
15171
  return this._galTiles.get(tileKey);
14964
15172
  }
15173
+ /**
15174
+ * Return an equatorial tile for rendering. While acquisition is suspended,
15175
+ * only tiles already active or cached are returned; missing tiles are not
15176
+ * created.
15177
+ */
15178
+ getRenderableTile(tileno, order, hips) {
15179
+ if (this._tileAcquisitionEnabled) {
15180
+ return this.getTile(tileno, order, hips);
15181
+ }
15182
+ const tileKey = this.key(order, tileno, hips);
15183
+ const activeTile = this._tiles.get(tileKey);
15184
+ if (activeTile)
15185
+ return activeTile;
15186
+ const cachedTile = this._cachedTiles.get(tileKey);
15187
+ if (!cachedTile)
15188
+ return undefined;
15189
+ this._tiles.set(tileKey, cachedTile);
15190
+ this._cachedTiles.delete(tileKey);
15191
+ cachedTile.resetCacheTime0();
15192
+ return cachedTile;
15193
+ }
15194
+ /** Galactic counterpart of getRenderableTile(). */
15195
+ getRenderableGalTile(tileno, order, hips) {
15196
+ if (this._tileAcquisitionEnabled) {
15197
+ return this.getGalTile(tileno, order, hips);
15198
+ }
15199
+ const tileKey = this.key(order, tileno, hips);
15200
+ const activeTile = this._galTiles.get(tileKey);
15201
+ if (activeTile)
15202
+ return activeTile;
15203
+ const cachedTile = this._galCachedTiles.get(tileKey);
15204
+ if (!cachedTile)
15205
+ return undefined;
15206
+ this._galTiles.set(tileKey, cachedTile);
15207
+ this._galCachedTiles.delete(tileKey);
15208
+ cachedTile.resetCacheTime0();
15209
+ return cachedTile;
15210
+ }
14965
15211
  /** Move a tile (equatorial or galactic) into cache. */
14966
15212
  moveTileToCache(tileno, order, hips) {
14967
15213
  const tileKey = this.key(order, tileno, hips);
@@ -15170,6 +15416,10 @@ class VisibleTilesManager {
15170
15416
  if (Global_js_1.default.insideSphere && order < 3) {
15171
15417
  order = 3;
15172
15418
  }
15419
+ // Visibility state is frame-local. Do not retain ancestors from previous
15420
+ // camera positions, otherwise a flyTo accumulates the whole travelled path.
15421
+ this._ancestorsMap.clear();
15422
+ this._galAncestorsMap.clear();
15173
15423
  this._ancestorsMap.set(order, []);
15174
15424
  this._galAncestorsMap.set(order, []);
15175
15425
  let pixels = [];
@@ -15186,8 +15436,10 @@ class VisibleTilesManager {
15186
15436
  }
15187
15437
  else {
15188
15438
  const geomhealpix = Global_js_1.default.getHealpix(order);
15189
- const maxX = webgl.canvas.width;
15190
- const maxY = webgl.canvas.height;
15439
+ const canvas = webgl.canvas;
15440
+ const canvasRect = canvas.getBoundingClientRect();
15441
+ const maxX = canvasRect.width;
15442
+ const maxY = canvasRect.height;
15191
15443
  // Sample a grid of screen points, project to the sphere, then to galactic
15192
15444
  for (let i = 0; i <= maxX; i += maxX / 30) {
15193
15445
  for (let j = 0; j <= maxY; j += maxY / 30) {