astro-viewer 3.12.0 → 3.13.1

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 (39) hide show
  1. package/dist/astroviewer.cjs +281 -25
  2. package/dist/astroviewer.cjs.map +1 -1
  3. package/dist/astroviewer.js +281 -25
  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/HiPS.d.ts +1 -0
  26. package/lib-esm/model/hips/HiPS.d.ts.map +1 -1
  27. package/lib-esm/model/hips/HiPS.js +4 -1
  28. package/lib-esm/model/hips/HiPS.js.map +1 -1
  29. package/lib-esm/model/hips/Tile.d.ts.map +1 -1
  30. package/lib-esm/model/hips/Tile.js +4 -2
  31. package/lib-esm/model/hips/Tile.js.map +1 -1
  32. package/lib-esm/model/hips/TileBuffer.d.ts +11 -0
  33. package/lib-esm/model/hips/TileBuffer.d.ts.map +1 -1
  34. package/lib-esm/model/hips/TileBuffer.js +47 -0
  35. package/lib-esm/model/hips/TileBuffer.js.map +1 -1
  36. package/lib-esm/model/hips/VisibleTilesManager.d.ts.map +1 -1
  37. package/lib-esm/model/hips/VisibleTilesManager.js +8 -2
  38. package/lib-esm/model/hips/VisibleTilesManager.js.map +1 -1
  39. 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");
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);
4496
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);
@@ -13561,6 +13758,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13561
13758
  };
13562
13759
  Object.defineProperty(exports, "__esModule", ({ value: true }));
13563
13760
  exports.HiPS = void 0;
13761
+ exports.normalizeHiPSBaseURL = normalizeHiPSBaseURL;
13564
13762
  /**
13565
13763
  * @author Fabrizio Giordano (Fab77)
13566
13764
  */
@@ -13570,6 +13768,9 @@ const ColorMaps_js_1 = __importDefault(__webpack_require__(619));
13570
13768
  const AncestorTile_js_1 = __importDefault(__webpack_require__(2885));
13571
13769
  const AllSky_js_1 = __importDefault(__webpack_require__(2368));
13572
13770
  const HiPSCoverage_js_1 = __webpack_require__(9912);
13771
+ function normalizeHiPSBaseURL(url) {
13772
+ return url.replace(/\/+$/, "");
13773
+ }
13573
13774
  const FITS_SCALE_FUNCTION_INDEX = {
13574
13775
  linear: 0,
13575
13776
  sqrt: 1,
@@ -13634,7 +13835,7 @@ class HiPS extends AbstractSkyEntity_js_1.AbstractSkyEntity {
13634
13835
  this.initGL(webgl);
13635
13836
  this._healpixGrid = healpixGrid;
13636
13837
  this._format = this.selectDefaultFormat(descriptor.imgFormats);
13637
- this._baseurl = descriptor.url;
13838
+ this._baseurl = normalizeHiPSBaseURL(descriptor.url);
13638
13839
  this._maxorder = descriptor.maxOrder;
13639
13840
  this._minorder = descriptor.minOrder;
13640
13841
  this._coverage = new HiPSCoverage_js_1.HiPSCoverage();
@@ -14769,8 +14970,10 @@ class Tile {
14769
14970
  continue;
14770
14971
  }
14771
14972
  const childTile = this._isGalacticHips
14772
- ? this._tileBuffer.getGalTile(childTileNo, childrenOrder, this._hips)
14773
- : this._tileBuffer.getTile(childTileNo, childrenOrder, this._hips);
14973
+ ? this._tileBuffer.getRenderableGalTile(childTileNo, childrenOrder, this._hips)
14974
+ : this._tileBuffer.getRenderableTile(childTileNo, childrenOrder, this._hips);
14975
+ if (!childTile)
14976
+ continue;
14774
14977
  childTile.draw(visibleOrder, visibleTilesMap, pMatrix, vMatrix, mMatrix, colorMapIdx, hipsShaderProgram);
14775
14978
  if (childTile.getReadyState()) {
14776
14979
  quadrantsToDraw.delete(c);
@@ -14821,6 +15024,7 @@ class TileBuffer {
14821
15024
  _webgl;
14822
15025
  _visibleTileManager;
14823
15026
  _hipsShaderProgram;
15027
+ _tileAcquisitionEnabled = true;
14824
15028
  constructor(minutesToLiveInCache = 1, webgl, hipsShaderProgram, visibleTileManager) {
14825
15029
  this._hipsShaderProgram = hipsShaderProgram;
14826
15030
  this._visibleTileManager = visibleTileManager;
@@ -14852,8 +15056,14 @@ class TileBuffer {
14852
15056
  }
14853
15057
  this._galActiveHiPS.set(hips, new Map());
14854
15058
  }
15059
+ /** Enable or suspend creation of missing HiPS tiles. */
15060
+ setTileAcquisitionEnabled(enabled) {
15061
+ this._tileAcquisitionEnabled = enabled;
15062
+ }
14855
15063
  /** Preload/add tile for every registered equatorial HiPS. */
14856
15064
  addTile(order, tileno) {
15065
+ if (!this._tileAcquisitionEnabled)
15066
+ return;
14857
15067
  for (const hips of this._activeHiPS.keys()) {
14858
15068
  if (order > hips.maxOrder) {
14859
15069
  continue;
@@ -14866,6 +15076,8 @@ class TileBuffer {
14866
15076
  }
14867
15077
  /** Preload/add tile for every registered galactic HiPS. */
14868
15078
  addGalTile(order, tileno) {
15079
+ if (!this._tileAcquisitionEnabled)
15080
+ return;
14869
15081
  for (const hips of this._galActiveHiPS.keys()) {
14870
15082
  if (order > hips.maxOrder) {
14871
15083
  continue;
@@ -14912,6 +15124,44 @@ class TileBuffer {
14912
15124
  }
14913
15125
  return this._galTiles.get(tileKey);
14914
15126
  }
15127
+ /**
15128
+ * Return an equatorial tile for rendering. While acquisition is suspended,
15129
+ * only tiles already active or cached are returned; missing tiles are not
15130
+ * created.
15131
+ */
15132
+ getRenderableTile(tileno, order, hips) {
15133
+ if (this._tileAcquisitionEnabled) {
15134
+ return this.getTile(tileno, order, hips);
15135
+ }
15136
+ const tileKey = this.key(order, tileno, hips);
15137
+ const activeTile = this._tiles.get(tileKey);
15138
+ if (activeTile)
15139
+ return activeTile;
15140
+ const cachedTile = this._cachedTiles.get(tileKey);
15141
+ if (!cachedTile)
15142
+ return undefined;
15143
+ this._tiles.set(tileKey, cachedTile);
15144
+ this._cachedTiles.delete(tileKey);
15145
+ cachedTile.resetCacheTime0();
15146
+ return cachedTile;
15147
+ }
15148
+ /** Galactic counterpart of getRenderableTile(). */
15149
+ getRenderableGalTile(tileno, order, hips) {
15150
+ if (this._tileAcquisitionEnabled) {
15151
+ return this.getGalTile(tileno, order, hips);
15152
+ }
15153
+ const tileKey = this.key(order, tileno, hips);
15154
+ const activeTile = this._galTiles.get(tileKey);
15155
+ if (activeTile)
15156
+ return activeTile;
15157
+ const cachedTile = this._galCachedTiles.get(tileKey);
15158
+ if (!cachedTile)
15159
+ return undefined;
15160
+ this._galTiles.set(tileKey, cachedTile);
15161
+ this._galCachedTiles.delete(tileKey);
15162
+ cachedTile.resetCacheTime0();
15163
+ return cachedTile;
15164
+ }
14915
15165
  /** Move a tile (equatorial or galactic) into cache. */
14916
15166
  moveTileToCache(tileno, order, hips) {
14917
15167
  const tileKey = this.key(order, tileno, hips);
@@ -15119,6 +15369,10 @@ class VisibleTilesManager {
15119
15369
  if (Global_js_1.default.insideSphere && order < 3) {
15120
15370
  order = 3;
15121
15371
  }
15372
+ // Visibility state is frame-local. Do not retain ancestors from previous
15373
+ // camera positions, otherwise a flyTo accumulates the whole travelled path.
15374
+ this._ancestorsMap.clear();
15375
+ this._galAncestorsMap.clear();
15122
15376
  this._ancestorsMap.set(order, []);
15123
15377
  this._galAncestorsMap.set(order, []);
15124
15378
  let pixels = [];
@@ -15135,8 +15389,10 @@ class VisibleTilesManager {
15135
15389
  }
15136
15390
  else {
15137
15391
  const geomhealpix = Global_js_1.default.getHealpix(order);
15138
- const maxX = webgl.canvas.width;
15139
- const maxY = webgl.canvas.height;
15392
+ const canvas = webgl.canvas;
15393
+ const canvasRect = canvas.getBoundingClientRect();
15394
+ const maxX = canvasRect.width;
15395
+ const maxY = canvasRect.height;
15140
15396
  // Sample a grid of screen points, project to the sphere, then to galactic
15141
15397
  for (let i = 0; i <= maxX; i += maxX / 30) {
15142
15398
  for (let j = 0; j <= maxY; j += maxY / 30) {