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
@@ -3657,6 +3657,7 @@ class AstroSphere {
3657
3657
  lockedNorthSouthDecDeg = null;
3658
3658
  keepCameraNorthUp = true;
3659
3659
  gridLabelContainers;
3660
+ fovAnimationResolve = null;
3660
3661
  constructor(canvas, webgl, options = {}) {
3661
3662
  console.log("[AstroSphere] new instance for canvas", canvas.id);
3662
3663
  // Keep global GL context (as in original JS)
@@ -3901,6 +3902,7 @@ class AstroSphere {
3901
3902
  canvas.setPointerCapture(event.pointerId);
3902
3903
  this.mouseDown = true;
3903
3904
  this._camera.cancelFlyTo();
3905
+ this.cancelFoVAnimation();
3904
3906
  const rect = canvas.getBoundingClientRect();
3905
3907
  this.lastMouseX = event.clientX - rect.left; // locale al canvas
3906
3908
  this.lastMouseY = event.clientY - rect.top; // locale al canvas
@@ -4031,6 +4033,7 @@ class AstroSphere {
4031
4033
  const currentFov = this._healpixGrid.getMinFoV();
4032
4034
  const zoomStep = this.computeZoomStep(currentFov, event.deltaY);
4033
4035
  this._camera.cancelFlyTo();
4036
+ this.cancelFoVAnimation();
4034
4037
  // Apply wheel zoom immediately and discard any queued inertia so reversing
4035
4038
  // direction feels responsive instead of "buffered".
4036
4039
  this.zoomInertia = 0;
@@ -4488,25 +4491,128 @@ class AstroSphere {
4488
4491
  throw new Error(`healpixGrid is ${this.healpixGrid}`);
4489
4492
  return FoVUtils_js_1.FoVUtils.getFoVPolygon(this._camera, this.canvas, this._healpixGrid, this._healpixGrid, this._webgl, this._perspectiveMatrixManager.pMatrix);
4490
4493
  }
4491
- changeFoV(deg) {
4492
- const distance = this._healpixGrid.getFoV().computeDistanceFromAngle(deg);
4493
- this._camera.translate(distance);
4494
+ validateFoV(deg) {
4495
+ if (!Number.isFinite(deg) || deg <= 0 || deg >= 180) {
4496
+ throw new RangeError(`FoV must be > 0 and < 180 degrees. Received ${deg}.`);
4497
+ }
4498
+ if (Global_js_1.default.insideSphere) {
4499
+ throw new Error("FoV navigation is currently supported only outside the sphere.");
4500
+ }
4501
+ }
4502
+ /**
4503
+ * Resolve the radial camera distance that produces the requested minimum FoV.
4504
+ *
4505
+ * The search temporarily samples radial distances, then restores the original
4506
+ * camera distance before returning. Callers can therefore either apply the
4507
+ * result immediately or animate toward it.
4508
+ */
4509
+ findRadialDistanceForFoV(deg) {
4510
+ this.validateFoV(deg);
4511
+ const originalDistance = this._camera.getRadialDistance();
4512
+ const minDistance = 1.000001;
4513
+ const maxDistance = 4.0;
4514
+ const tolerance = Math.max(1e-5, deg * 0.001);
4515
+ const maxIterations = 40;
4516
+ let low = minDistance;
4517
+ let high = maxDistance;
4518
+ let bestDistance = originalDistance;
4519
+ let bestError = Number.POSITIVE_INFINITY;
4520
+ for (let iteration = 0; iteration < maxIterations; iteration += 1) {
4521
+ const candidateDistance = (low + high) / 2;
4522
+ this._camera.setRadialDistance(candidateDistance);
4523
+ 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);
4524
+ const candidateFoV = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
4525
+ const measuredFoV = candidateFoV.minFoV;
4526
+ const error = Math.abs(measuredFoV - deg);
4527
+ if (error < bestError) {
4528
+ bestError = error;
4529
+ bestDistance = candidateDistance;
4530
+ }
4531
+ if (error <= tolerance) {
4532
+ break;
4533
+ }
4534
+ if (measuredFoV >= deg || measuredFoV >= 179.999) {
4535
+ high = candidateDistance;
4536
+ }
4537
+ else {
4538
+ low = candidateDistance;
4539
+ }
4540
+ }
4541
+ this._camera.setRadialDistance(originalDistance);
4542
+ 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);
4494
4543
  this.fov = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
4495
4544
  this._camera.refreshFoV(this.fov.minFoV);
4545
+ return bestDistance;
4546
+ }
4547
+ finishFoVAnimation() {
4548
+ const resolve = this.fovAnimationResolve;
4549
+ this.fovAnimationResolve = null;
4550
+ resolve?.();
4551
+ }
4552
+ cancelFoVAnimation() {
4553
+ this._camera.cancelRadialAnimation();
4554
+ this.finishFoVAnimation();
4555
+ }
4556
+ /** Set the minimum angular field of view immediately, in degrees. */
4557
+ setFoV(deg) {
4558
+ this.validateFoV(deg);
4559
+ this.cancelFoVAnimation();
4560
+ this._camera.cancelFlyTo();
4561
+ this.zoomInertia = 0;
4562
+ const targetDistance = this.findRadialDistanceForFoV(deg);
4563
+ this._camera.setRadialDistance(targetDistance);
4564
+ 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);
4565
+ this.fov = this._healpixGrid.refreshFoV(this._camera, this._perspectiveMatrixManager.pMatrix);
4566
+ this._camera.refreshFoV(this.fov.minFoV);
4567
+ this.lastCameraMotionAt = performance.now();
4568
+ this._cameraStatusChanged = true;
4569
+ this.emitCameraChanged("set-fov");
4496
4570
  }
4571
+ async flyToFoV(deg, durationMs = 1200) {
4572
+ this.validateFoV(deg);
4573
+ if (!Number.isFinite(durationMs) || durationMs < 0) {
4574
+ throw new RangeError(`FoV animation duration must be >= 0 ms. Received ${durationMs}.`);
4575
+ }
4576
+ if (durationMs === 0) {
4577
+ this.setFoV(deg);
4578
+ return;
4579
+ }
4580
+ this.cancelFoVAnimation();
4581
+ this.zoomInertia = 0;
4582
+ const targetDistance = this.findRadialDistanceForFoV(deg);
4583
+ this._camera.flyToRadialDistance(targetDistance, durationMs);
4584
+ this.lastCameraMotionAt = performance.now();
4585
+ this._cameraStatusChanged = true;
4586
+ return new Promise((resolve) => {
4587
+ this.fovAnimationResolve = resolve;
4588
+ });
4589
+ }
4590
+ async zoomIn(steps = 1, durationMs = 500) {
4591
+ this.validateZoomSteps(steps);
4592
+ const targetFoV = this.getFoV().minFoV / Math.pow(2, steps);
4593
+ return this.flyToFoV(targetFoV, durationMs);
4594
+ }
4595
+ async zoomOut(steps = 1, durationMs = 500) {
4596
+ this.validateZoomSteps(steps);
4597
+ const targetFoV = this.getFoV().minFoV * Math.pow(2, steps);
4598
+ return this.flyToFoV(targetFoV, durationMs);
4599
+ }
4600
+ validateZoomSteps(steps) {
4601
+ if (!Number.isInteger(steps) || steps < 1) {
4602
+ throw new RangeError(`Zoom steps must be a positive integer. Received ${steps}.`);
4603
+ }
4604
+ }
4605
+ /** @deprecated Use setFoV(). */
4606
+ changeFoV(deg) {
4607
+ this.setFoV(deg);
4608
+ }
4609
+ /** @deprecated Use setFoV(). */
4497
4610
  changeFoV2(deg) {
4498
- const newCameraPos = this._healpixGrid
4499
- .getFoV()
4500
- .computeCameraPositionForFoV(deg);
4501
- this._camera.setCameraPosition(newCameraPos);
4611
+ this.setFoV(deg);
4502
4612
  }
4613
+ /** @deprecated Use setFoV(). */
4503
4614
  changeFoV3(deg) {
4504
- const newPos = this._healpixGrid
4505
- .getFoV()
4506
- .computeCameraPositionForAngularDiameter(deg);
4507
- this._camera.setCameraPosition(newPos);
4508
- // Recompute projection after moving the camera
4509
- this._perspectiveMatrixManager.computePerspectiveMatrix(this.canvas, this._camera, Config_js_1.bootSetup.camera_fov_deg, Config_js_1.bootSetup.camera_near_plane, false);
4615
+ this.setFoV(deg);
4510
4616
  }
4511
4617
  getInsideSphere() {
4512
4618
  return Global_js_1.default.insideSphere;
@@ -4516,6 +4622,7 @@ class AstroSphere {
4516
4622
  this.inertiaX = 0;
4517
4623
  this.inertiaY = 0;
4518
4624
  this.zoomInertia = 0;
4625
+ this.cancelFoVAnimation();
4519
4626
  Global_js_1.default.insideSphere = !Global_js_1.default.insideSphere;
4520
4627
  // console.log(global.insideSphere)
4521
4628
  this._camera.toggleInsideSphere();
@@ -4691,6 +4798,14 @@ class AstroSphere {
4691
4798
  this.updateCentralPoint();
4692
4799
  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);
4693
4800
  }
4801
+ const fovFlying = this._camera.updateRadialAnimation(now);
4802
+ const fovAnimationFinished = fovFlying && !this._camera.isRadialAnimationActive();
4803
+ if (fovFlying) {
4804
+ this.zoomInertia = 0;
4805
+ this.lastCameraMotionAt = now;
4806
+ this._cameraStatusChanged = true;
4807
+ 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);
4808
+ }
4694
4809
  // Zoom inertia
4695
4810
  if (this.zoomInertia !== 0) {
4696
4811
  if (Math.abs(this.zoomInertia) > 0.0001) {
@@ -4739,6 +4854,9 @@ class AstroSphere {
4739
4854
  this._camera.refreshFoV(this.fov.minFoV);
4740
4855
  this.prevFov = this.fov.minFoV;
4741
4856
  }
4857
+ if (fovAnimationFinished) {
4858
+ this.finishFoVAnimation();
4859
+ }
4742
4860
  // Se la camera è ruotata (anche solo per inerzia), aggiorna punto centrale + emetti cameraChanged
4743
4861
  if (cameraRotated) {
4744
4862
  // Ricalcola il punto centrale
@@ -4780,6 +4898,10 @@ class AstroSphere {
4780
4898
  this._webgl.cullFace(Global_js_1.default.insideSphere ? this._webgl.FRONT : this._webgl.BACK);
4781
4899
  this._webgl.blendFunc(this._webgl.SRC_ALPHA, this._webgl.ONE_MINUS_SRC_ALPHA);
4782
4900
  if (this._activeBaseLayer === "hips" && this._activeHiPS) {
4901
+ // During directional flyTo navigation, keep visibility current but render
4902
+ // only HiPS tiles that are already available. Final-view refinement
4903
+ // resumes automatically on the first frame after the flight.
4904
+ this._healpixGrid.visibleTilesManager.tileBuffer.setTileAcquisitionEnabled(!cameraFlying);
4783
4905
  const maxHiPSOrder = this._activeHiPSLayers.length > 0
4784
4906
  ? Math.max(...this._activeHiPSLayers.map((hips) => hips.maxOrder))
4785
4907
  : this._activeHiPS.maxOrder;
@@ -4797,6 +4919,7 @@ class AstroSphere {
4797
4919
  Math.abs(this.zoomInertia) > 0.0001 ||
4798
4920
  Math.abs(this.inertiaX) > 0.02 ||
4799
4921
  Math.abs(this.inertiaY) > 0.02 ||
4922
+ this._camera.isRadialAnimationActive() ||
4800
4923
  nowForGrid - this.lastCameraMotionAt < 220;
4801
4924
  const skyEntityDrawInput = {
4802
4925
  fovDeg: stableFovDeg,
@@ -5331,14 +5454,29 @@ class AstroViewer {
5331
5454
  getFoVPolygon() {
5332
5455
  return this.astroSphere.getFoVPolygon();
5333
5456
  }
5457
+ setFoV(deg) {
5458
+ this.astroSphere.setFoV(deg);
5459
+ }
5460
+ flyToFoV(deg, durationMs = 1200) {
5461
+ return this.astroSphere.flyToFoV(deg, durationMs);
5462
+ }
5463
+ zoomIn(steps = 1, durationMs = 500) {
5464
+ return this.astroSphere.zoomIn(steps, durationMs);
5465
+ }
5466
+ zoomOut(steps = 1, durationMs = 500) {
5467
+ return this.astroSphere.zoomOut(steps, durationMs);
5468
+ }
5469
+ /** @deprecated Use setFoV(). */
5334
5470
  changeFoV(deg) {
5335
- (this, this.astroSphere.changeFoV(deg));
5471
+ this.setFoV(deg);
5336
5472
  }
5473
+ /** @deprecated Use setFoV(). */
5337
5474
  changeFoV2(deg) {
5338
- (this, this.astroSphere.changeFoV2(deg));
5475
+ this.setFoV(deg);
5339
5476
  }
5477
+ /** @deprecated Use setFoV(). */
5340
5478
  changeFoV3(deg) {
5341
- (this, this.astroSphere.changeFoV3(deg));
5479
+ this.setFoV(deg);
5342
5480
  }
5343
5481
  getInsideSphere() {
5344
5482
  return this.astroSphere.getInsideSphere();
@@ -5590,6 +5728,7 @@ class Camera {
5590
5728
  lockRotY = false;
5591
5729
  lockRotZ = false;
5592
5730
  flyToAnimation = null;
5731
+ radialAnimation = null;
5593
5732
  constructor(in_position, in_sphere) {
5594
5733
  this.init(in_position, in_sphere);
5595
5734
  }
@@ -5724,6 +5863,42 @@ class Camera {
5724
5863
  cancelFlyTo() {
5725
5864
  this.flyToAnimation = null;
5726
5865
  }
5866
+ flyToRadialDistance(distance, durationMs = 1200) {
5867
+ if (!Number.isFinite(distance) || distance <= 1) {
5868
+ throw new RangeError(`Camera radial distance must be > 1. Received ${distance}.`);
5869
+ }
5870
+ this.radialAnimation = {
5871
+ startDistance: this.cam_pos[2],
5872
+ targetDistance: distance,
5873
+ startTime: performance.now(),
5874
+ durationMs: Math.max(1, durationMs),
5875
+ };
5876
+ }
5877
+ updateRadialAnimation(now) {
5878
+ const animation = this.radialAnimation;
5879
+ if (!animation) {
5880
+ return false;
5881
+ }
5882
+ const elapsed = now - animation.startTime;
5883
+ const progress = Math.min(1, elapsed / animation.durationMs);
5884
+ const easedProgress = progress < 0.5
5885
+ ? 4 * progress * progress * progress
5886
+ : 1 - Math.pow(-2 * progress + 2, 3) / 2;
5887
+ const distance = animation.startDistance +
5888
+ (animation.targetDistance - animation.startDistance) * easedProgress;
5889
+ this.setRadialDistance(distance);
5890
+ if (progress >= 1) {
5891
+ this.setRadialDistance(animation.targetDistance);
5892
+ this.radialAnimation = null;
5893
+ }
5894
+ return true;
5895
+ }
5896
+ isRadialAnimationActive() {
5897
+ return this.radialAnimation !== null;
5898
+ }
5899
+ cancelRadialAnimation() {
5900
+ this.radialAnimation = null;
5901
+ }
5727
5902
  toggleInsideSphere() {
5728
5903
  // if (inside !== global.insideSphere) {
5729
5904
  // global.insideSphere = inside;
@@ -5973,6 +6148,24 @@ class Camera {
5973
6148
  // Recompute view: vMatrix = inv(T) * inv(R)
5974
6149
  this.refreshViewMatrix();
5975
6150
  }
6151
+ /**
6152
+ * Set the radial camera distance while preserving the current pointing.
6153
+ *
6154
+ * Outside-sphere navigation stores the camera radius in cam_pos[2];
6155
+ * changing only that component is the same radial motion used by zoom(),
6156
+ * without changing the current rotation matrix.
6157
+ */
6158
+ setRadialDistance(distance) {
6159
+ if (!Number.isFinite(distance) || distance <= 1) {
6160
+ throw new RangeError(`Camera radial distance must be > 1. Received ${distance}.`);
6161
+ }
6162
+ this.cam_pos[2] = distance;
6163
+ gl_matrix_1.mat4.translate(this.T, gl_matrix_1.mat4.create(), this.cam_pos);
6164
+ this.refreshViewMatrix();
6165
+ }
6166
+ getRadialDistance() {
6167
+ return this.cam_pos[2];
6168
+ }
5976
6169
  getCameraAngle() {
5977
6170
  const [x, y, z] = this.getCameraPosition();
5978
6171
  const posVec = gl_matrix_1.vec3.fromValues(x, y, z);
@@ -12961,8 +13154,10 @@ class AllSky {
12961
13154
  continue;
12962
13155
  }
12963
13156
  const childTile = this._isGalacticHips
12964
- ? this._tileBuffer.getGalTile(tileno, childrenOrder, this._hips)
12965
- : this._tileBuffer.getTile(tileno, childrenOrder, this._hips);
13157
+ ? this._tileBuffer.getRenderableGalTile(tileno, childrenOrder, this._hips)
13158
+ : this._tileBuffer.getRenderableTile(tileno, childrenOrder, this._hips);
13159
+ if (!childTile)
13160
+ continue;
12966
13161
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, this._hipsShaderProgram);
12967
13162
  if (childTile.getReadyState()) {
12968
13163
  allSkyTiles2Skip.push(tileno);
@@ -13250,8 +13445,10 @@ class AncestorTile {
13250
13445
  continue;
13251
13446
  }
13252
13447
  const childTile = this._isGalacticHips
13253
- ? this._tileBuffer.getGalTile(childTileNo, childrenOrder, this._hips)
13254
- : this._tileBuffer.getTile(childTileNo, childrenOrder, this._hips);
13448
+ ? this._tileBuffer.getRenderableGalTile(childTileNo, childrenOrder, this._hips)
13449
+ : this._tileBuffer.getRenderableTile(childTileNo, childrenOrder, this._hips);
13450
+ if (!childTile)
13451
+ continue;
13255
13452
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, this._hipsShaderProgram);
13256
13453
  if (childTile.getReadyState()) {
13257
13454
  quadrantsToDraw.delete(c);
@@ -14769,8 +14966,10 @@ class Tile {
14769
14966
  continue;
14770
14967
  }
14771
14968
  const childTile = this._isGalacticHips
14772
- ? this._tileBuffer.getGalTile(childTileNo, childrenOrder, this._hips)
14773
- : this._tileBuffer.getTile(childTileNo, childrenOrder, this._hips);
14969
+ ? this._tileBuffer.getRenderableGalTile(childTileNo, childrenOrder, this._hips)
14970
+ : this._tileBuffer.getRenderableTile(childTileNo, childrenOrder, this._hips);
14971
+ if (!childTile)
14972
+ continue;
14774
14973
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, hipsShaderProgram);
14775
14974
  if (childTile.getReadyState()) {
14776
14975
  quadrantsToDraw.delete(c);
@@ -14821,6 +15020,7 @@ class TileBuffer {
14821
15020
  _webgl;
14822
15021
  _visibleTileManager;
14823
15022
  _hipsShaderProgram;
15023
+ _tileAcquisitionEnabled = true;
14824
15024
  constructor(minutesToLiveInCache = 1, webgl, hipsShaderProgram, visibleTileManager) {
14825
15025
  this._hipsShaderProgram = hipsShaderProgram;
14826
15026
  this._visibleTileManager = visibleTileManager;
@@ -14852,8 +15052,14 @@ class TileBuffer {
14852
15052
  }
14853
15053
  this._galActiveHiPS.set(hips, new Map());
14854
15054
  }
15055
+ /** Enable or suspend creation of missing HiPS tiles. */
15056
+ setTileAcquisitionEnabled(enabled) {
15057
+ this._tileAcquisitionEnabled = enabled;
15058
+ }
14855
15059
  /** Preload/add tile for every registered equatorial HiPS. */
14856
15060
  addTile(order, tileno) {
15061
+ if (!this._tileAcquisitionEnabled)
15062
+ return;
14857
15063
  for (const hips of this._activeHiPS.keys()) {
14858
15064
  if (order > hips.maxOrder) {
14859
15065
  continue;
@@ -14866,6 +15072,8 @@ class TileBuffer {
14866
15072
  }
14867
15073
  /** Preload/add tile for every registered galactic HiPS. */
14868
15074
  addGalTile(order, tileno) {
15075
+ if (!this._tileAcquisitionEnabled)
15076
+ return;
14869
15077
  for (const hips of this._galActiveHiPS.keys()) {
14870
15078
  if (order > hips.maxOrder) {
14871
15079
  continue;
@@ -14912,6 +15120,44 @@ class TileBuffer {
14912
15120
  }
14913
15121
  return this._galTiles.get(tileKey);
14914
15122
  }
15123
+ /**
15124
+ * Return an equatorial tile for rendering. While acquisition is suspended,
15125
+ * only tiles already active or cached are returned; missing tiles are not
15126
+ * created.
15127
+ */
15128
+ getRenderableTile(tileno, order, hips) {
15129
+ if (this._tileAcquisitionEnabled) {
15130
+ return this.getTile(tileno, order, hips);
15131
+ }
15132
+ const tileKey = this.key(order, tileno, hips);
15133
+ const activeTile = this._tiles.get(tileKey);
15134
+ if (activeTile)
15135
+ return activeTile;
15136
+ const cachedTile = this._cachedTiles.get(tileKey);
15137
+ if (!cachedTile)
15138
+ return undefined;
15139
+ this._tiles.set(tileKey, cachedTile);
15140
+ this._cachedTiles.delete(tileKey);
15141
+ cachedTile.resetCacheTime0();
15142
+ return cachedTile;
15143
+ }
15144
+ /** Galactic counterpart of getRenderableTile(). */
15145
+ getRenderableGalTile(tileno, order, hips) {
15146
+ if (this._tileAcquisitionEnabled) {
15147
+ return this.getGalTile(tileno, order, hips);
15148
+ }
15149
+ const tileKey = this.key(order, tileno, hips);
15150
+ const activeTile = this._galTiles.get(tileKey);
15151
+ if (activeTile)
15152
+ return activeTile;
15153
+ const cachedTile = this._galCachedTiles.get(tileKey);
15154
+ if (!cachedTile)
15155
+ return undefined;
15156
+ this._galTiles.set(tileKey, cachedTile);
15157
+ this._galCachedTiles.delete(tileKey);
15158
+ cachedTile.resetCacheTime0();
15159
+ return cachedTile;
15160
+ }
14915
15161
  /** Move a tile (equatorial or galactic) into cache. */
14916
15162
  moveTileToCache(tileno, order, hips) {
14917
15163
  const tileKey = this.key(order, tileno, hips);
@@ -15119,6 +15365,10 @@ class VisibleTilesManager {
15119
15365
  if (Global_js_1.default.insideSphere && order < 3) {
15120
15366
  order = 3;
15121
15367
  }
15368
+ // Visibility state is frame-local. Do not retain ancestors from previous
15369
+ // camera positions, otherwise a flyTo accumulates the whole travelled path.
15370
+ this._ancestorsMap.clear();
15371
+ this._galAncestorsMap.clear();
15122
15372
  this._ancestorsMap.set(order, []);
15123
15373
  this._galAncestorsMap.set(order, []);
15124
15374
  let pixels = [];
@@ -15135,8 +15385,10 @@ class VisibleTilesManager {
15135
15385
  }
15136
15386
  else {
15137
15387
  const geomhealpix = Global_js_1.default.getHealpix(order);
15138
- const maxX = webgl.canvas.width;
15139
- const maxY = webgl.canvas.height;
15388
+ const canvas = webgl.canvas;
15389
+ const canvasRect = canvas.getBoundingClientRect();
15390
+ const maxX = canvasRect.width;
15391
+ const maxY = canvasRect.height;
15140
15392
  // Sample a grid of screen points, project to the sphere, then to galactic
15141
15393
  for (let i = 0; i <= maxX; i += maxX / 30) {
15142
15394
  for (let j = 0; j <= maxY; j += maxY / 30) {