astro-viewer 3.10.0 → 3.11.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.
@@ -53,6 +53,7 @@ __webpack_require__.d(vec3_namespaceObject, {
53
53
  dot: () => (dot),
54
54
  fromValues: () => (vec3_fromValues),
55
55
  length: () => (vec3_length),
56
+ lerp: () => (lerp),
56
57
  normalize: () => (normalize),
57
58
  scale: () => (vec3_scale),
58
59
  scaleAndAdd: () => (scaleAndAdd),
@@ -3906,6 +3907,7 @@ class AstroSphere {
3906
3907
  const handleMouseDown = (event) => {
3907
3908
  canvas.setPointerCapture(event.pointerId);
3908
3909
  this.mouseDown = true;
3910
+ this._camera.cancelFlyTo();
3909
3911
  const rect = canvas.getBoundingClientRect();
3910
3912
  this.lastMouseX = event.clientX - rect.left; // locale al canvas
3911
3913
  this.lastMouseY = event.clientY - rect.top; // locale al canvas
@@ -4032,6 +4034,7 @@ class AstroSphere {
4032
4034
  const handleMouseWheel = (event) => {
4033
4035
  const currentFov = this._healpixGrid.getMinFoV();
4034
4036
  const zoomStep = this.computeZoomStep(currentFov, event.deltaY);
4037
+ this._camera.cancelFlyTo();
4035
4038
  // Apply wheel zoom immediately and discard any queued inertia so reversing
4036
4039
  // direction feels responsive instead of "buffered".
4037
4040
  this.zoomInertia = 0;
@@ -4297,6 +4300,9 @@ class AstroSphere {
4297
4300
  goTo(raDeg, decDeg) {
4298
4301
  this._camera.goTo(raDeg, decDeg);
4299
4302
  }
4303
+ flyTo(raDeg, decDeg, durationMs = 1200) {
4304
+ this._camera.flyTo(raDeg, decDeg, durationMs);
4305
+ }
4300
4306
  getActiveCoordinateMode() {
4301
4307
  if (this._activeBaseLayer === "xyz") {
4302
4308
  return "lonlat";
@@ -4532,6 +4538,16 @@ class AstroSphere {
4532
4538
  let PHI = 0;
4533
4539
  this._webgl.viewport(0, 0, this._webgl.drawingBufferWidth, this._webgl.drawingBufferHeight);
4534
4540
  this._webgl.clear(this._webgl.COLOR_BUFFER_BIT | this._webgl.DEPTH_BUFFER_BIT);
4541
+ const now = performance.now();
4542
+ const cameraFlying = this._camera.updateFlyTo(now);
4543
+ if (cameraFlying) {
4544
+ this.inertiaX = 0;
4545
+ this.inertiaY = 0;
4546
+ this.lastCameraMotionAt = now;
4547
+ this._cameraStatusChanged = true;
4548
+ this.updateCentralPoint();
4549
+ 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);
4550
+ }
4535
4551
  // Zoom inertia
4536
4552
  if (this.zoomInertia !== 0) {
4537
4553
  if (Math.abs(this.zoomInertia) > 0.0001) {
@@ -5063,6 +5079,9 @@ class AstroViewer {
5063
5079
  // console.log(`AstroViewer.goTo goto(${raDeg}, ${decDeg})`)
5064
5080
  this.astroSphere.goTo(raDeg, decDeg);
5065
5081
  }
5082
+ flyTo(raDeg, decDeg, durationMs = 1200) {
5083
+ this.astroSphere.flyTo(raDeg, decDeg, durationMs);
5084
+ }
5066
5085
  getActiveCoordinateMode() {
5067
5086
  return this.astroSphere.getActiveCoordinateMode();
5068
5087
  }
@@ -5385,6 +5404,7 @@ class Camera {
5385
5404
  lockRotX = false;
5386
5405
  lockRotY = false;
5387
5406
  lockRotZ = false;
5407
+ flyToAnimation = null;
5388
5408
  constructor(in_position, in_sphere) {
5389
5409
  this.init(in_position, in_sphere);
5390
5410
  }
@@ -5394,7 +5414,11 @@ class Camera {
5394
5414
  this.vMatrix = gl_matrix_1.mat4.create();
5395
5415
  this.T = gl_matrix_1.mat4.create();
5396
5416
  this.R = gl_matrix_1.mat4.create();
5397
- gl_matrix_1.mat4.translate(this.T, this.T, [this.cam_pos[0], this.cam_pos[1], this.cam_pos[2]]);
5417
+ gl_matrix_1.mat4.translate(this.T, this.T, [
5418
+ this.cam_pos[0],
5419
+ this.cam_pos[1],
5420
+ this.cam_pos[2],
5421
+ ]);
5398
5422
  // reset helpers
5399
5423
  this.FoV = this.previousFoV = 180.0;
5400
5424
  this.move = gl_matrix_1.vec3.clone([0, 0, 0]);
@@ -5403,11 +5427,15 @@ class Camera {
5403
5427
  this.goTo(raDeg, decDeg);
5404
5428
  }
5405
5429
  goTo(raDeg, decDeg) {
5430
+ this.cancelFlyTo();
5406
5431
  this.goToPhiTheta((0, Utils_js_1.astroDegToSpherical)(raDeg, decDeg));
5407
5432
  }
5408
5433
  goToPhiTheta(ptDeg) {
5409
5434
  const xyz = (0, Utils_js_1.sphericalToCartesian)(ptDeg.phi, ptDeg.theta, this.cam_pos[2]);
5410
- const targetDirection = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), gl_matrix_1.vec3.fromValues(xyz[0], xyz[1], xyz[2]));
5435
+ this.applyCameraPosition(gl_matrix_1.vec3.fromValues(xyz[0], xyz[1], xyz[2]));
5436
+ }
5437
+ applyCameraPosition(cameraPosition) {
5438
+ const targetDirection = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), cameraPosition);
5411
5439
  const celestialNorth = gl_matrix_1.vec3.fromValues(0.0, 0.0, 1.0);
5412
5440
  const northProjection = gl_matrix_1.vec3.scale(gl_matrix_1.vec3.create(), targetDirection, gl_matrix_1.vec3.dot(celestialNorth, targetDirection));
5413
5441
  const cameraUp = gl_matrix_1.vec3.subtract(gl_matrix_1.vec3.create(), celestialNorth, northProjection);
@@ -5418,9 +5446,13 @@ class Camera {
5418
5446
  gl_matrix_1.vec3.normalize(cameraUp, cameraUp);
5419
5447
  }
5420
5448
  let cameraMatrix = gl_matrix_1.mat4.create();
5421
- cameraMatrix = gl_matrix_1.mat4.translate(cameraMatrix, cameraMatrix, gl_matrix_1.vec3.fromValues(xyz[0], xyz[1], xyz[2]));
5449
+ cameraMatrix = gl_matrix_1.mat4.translate(cameraMatrix, cameraMatrix, cameraPosition);
5422
5450
  const focusPoint = [0.0, 0.0, 0.0];
5423
- const cameraPos = [cameraMatrix[12], cameraMatrix[13], cameraMatrix[14]];
5451
+ const cameraPos = [
5452
+ cameraMatrix[12],
5453
+ cameraMatrix[13],
5454
+ cameraMatrix[14],
5455
+ ];
5424
5456
  cameraMatrix = gl_matrix_1.mat4.targetTo(cameraMatrix, cameraPos, focusPoint, cameraUp);
5425
5457
  this.R = gl_matrix_1.mat4.clone(cameraMatrix);
5426
5458
  this.R[12] = 0;
@@ -5432,6 +5464,81 @@ class Camera {
5432
5464
  }
5433
5465
  this.vMatrix = viewMatrix;
5434
5466
  }
5467
+ flyTo(raDeg, decDeg, durationMs = 1200) {
5468
+ const targetSpherical = (0, Utils_js_1.astroDegToSpherical)(raDeg, decDeg);
5469
+ const targetPositionValues = (0, Utils_js_1.sphericalToCartesian)(targetSpherical.phi, targetSpherical.theta, this.cam_pos[2]);
5470
+ const targetPosition = gl_matrix_1.vec3.fromValues(targetPositionValues[0], targetPositionValues[1], targetPositionValues[2]);
5471
+ const currentPosition = this.getCameraPosition();
5472
+ const startDirection = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), gl_matrix_1.vec3.fromValues(currentPosition[0], currentPosition[1], currentPosition[2]));
5473
+ const targetDirection = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), targetPosition);
5474
+ this.flyToAnimation = {
5475
+ startDirection,
5476
+ targetDirection,
5477
+ targetPosition,
5478
+ startTime: performance.now(),
5479
+ durationMs: Math.max(1, durationMs),
5480
+ };
5481
+ }
5482
+ updateFlyTo(now) {
5483
+ const animation = this.flyToAnimation;
5484
+ if (!animation) {
5485
+ return false;
5486
+ }
5487
+ const elapsed = now - animation.startTime;
5488
+ const progress = Math.min(1, elapsed / animation.durationMs);
5489
+ const easedProgress = progress < 0.5
5490
+ ? 4 * progress * progress * progress
5491
+ : 1 - Math.pow(-2 * progress + 2, 3) / 2;
5492
+ const direction = this.slerpDirection(animation.startDirection, animation.targetDirection, easedProgress);
5493
+ const radius = Math.abs(this.cam_pos[2]);
5494
+ const cameraPosition = gl_matrix_1.vec3.scale(gl_matrix_1.vec3.create(), direction, radius);
5495
+ this.applyCameraPosition(cameraPosition);
5496
+ if (progress >= 1) {
5497
+ this.applyCameraPosition(animation.targetPosition);
5498
+ this.flyToAnimation = null;
5499
+ }
5500
+ return true;
5501
+ }
5502
+ slerpDirection(start, end, t) {
5503
+ const dot = Math.max(-1, Math.min(1, gl_matrix_1.vec3.dot(start, end)));
5504
+ /*
5505
+ * For almost identical directions, normalized linear interpolation is
5506
+ * numerically more stable than the general SLERP formula.
5507
+ */
5508
+ if (dot > 0.999999) {
5509
+ const result = gl_matrix_1.vec3.lerp(gl_matrix_1.vec3.create(), start, end, t);
5510
+ return gl_matrix_1.vec3.normalize(result, result);
5511
+ }
5512
+ /*
5513
+ * The normal SLERP formula becomes singular for antipodal vectors because
5514
+ * sin(pi) = 0. In that case choose a stable vector orthogonal to start and
5515
+ * rotate through that great circle.
5516
+ */
5517
+ if (dot < -0.999999) {
5518
+ const referenceAxis = Math.abs(start[0]) < 0.9
5519
+ ? gl_matrix_1.vec3.fromValues(1, 0, 0)
5520
+ : gl_matrix_1.vec3.fromValues(0, 1, 0);
5521
+ const orthogonal = gl_matrix_1.vec3.cross(gl_matrix_1.vec3.create(), start, referenceAxis);
5522
+ gl_matrix_1.vec3.normalize(orthogonal, orthogonal);
5523
+ const angle = Math.PI * t;
5524
+ const result = gl_matrix_1.vec3.create();
5525
+ gl_matrix_1.vec3.scale(result, start, Math.cos(angle));
5526
+ gl_matrix_1.vec3.scaleAndAdd(result, result, orthogonal, Math.sin(angle));
5527
+ return gl_matrix_1.vec3.normalize(result, result);
5528
+ }
5529
+ const angle = Math.acos(dot);
5530
+ const sinAngle = Math.sin(angle);
5531
+ const startWeight = Math.sin((1 - t) * angle) / sinAngle;
5532
+ const endWeight = Math.sin(t * angle) / sinAngle;
5533
+ const result = gl_matrix_1.vec3.create();
5534
+ result[0] = start[0] * startWeight + end[0] * endWeight;
5535
+ result[1] = start[1] * startWeight + end[1] * endWeight;
5536
+ result[2] = start[2] * startWeight + end[2] * endWeight;
5537
+ return gl_matrix_1.vec3.normalize(result, result);
5538
+ }
5539
+ cancelFlyTo() {
5540
+ this.flyToAnimation = null;
5541
+ }
5435
5542
  toggleInsideSphere() {
5436
5543
  // if (inside !== global.insideSphere) {
5437
5544
  // global.insideSphere = inside;
@@ -5689,9 +5796,15 @@ class Camera {
5689
5796
  clearRotationLock() {
5690
5797
  this.lockRotX = this.lockRotY = this.lockRotZ = false;
5691
5798
  }
5692
- isRotationLockedX() { return this.lockRotX; }
5693
- isRotationLockedY() { return this.lockRotY; }
5694
- isRotationLockedZ() { return this.lockRotZ; }
5799
+ isRotationLockedX() {
5800
+ return this.lockRotX;
5801
+ }
5802
+ isRotationLockedY() {
5803
+ return this.lockRotY;
5804
+ }
5805
+ isRotationLockedZ() {
5806
+ return this.lockRotZ;
5807
+ }
5695
5808
  }
5696
5809
  exports["default"] = Camera;
5697
5810
 
@@ -7903,6 +8016,7 @@ class Source {
7903
8016
  _h_pix;
7904
8017
  _shapesize;
7905
8018
  _brightnessFactor;
8019
+ _mediaStyle;
7906
8020
  /**
7907
8021
  * @param in_point Point.js (Cartesian/RA-Dec wrapper)
7908
8022
  * @param in_details Optional array of key/value metadata
@@ -7955,6 +8069,12 @@ class Source {
7955
8069
  set brightnessFactor(factor) {
7956
8070
  this._brightnessFactor = factor;
7957
8071
  }
8072
+ get mediaStyle() {
8073
+ return this._mediaStyle;
8074
+ }
8075
+ set mediaStyle(style) {
8076
+ this._mediaStyle = style;
8077
+ }
7958
8078
  }
7959
8079
  exports.Source = Source;
7960
8080
 
@@ -8182,6 +8302,9 @@ exports.SphereFoV = SphereFoV;
8182
8302
  *
8183
8303
  * See LICENSE.md, LICENSE-AGPL.md, and LICENSE-COMMERCIAL.md for details.
8184
8304
  */
8305
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8306
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8307
+ };
8185
8308
  Object.defineProperty(exports, "__esModule", ({ value: true }));
8186
8309
  exports.CatalogueGL = void 0;
8187
8310
  const Source_js_1 = __webpack_require__(146);
@@ -8190,6 +8313,8 @@ const CoordsType_js_1 = __webpack_require__(8145);
8190
8313
  const Utils_js_1 = __webpack_require__(7930);
8191
8314
  const CatalogueShaderProgram_js_1 = __webpack_require__(3559);
8192
8315
  const MetadataManager_js_1 = __webpack_require__(5403);
8316
+ const gl_matrix_1 = __webpack_require__(3890);
8317
+ const Global_js_1 = __importDefault(__webpack_require__(4382));
8193
8318
  class CatalogueGL {
8194
8319
  _kind = "CatalogueGL";
8195
8320
  static ELEM_SIZE = 6;
@@ -8221,6 +8346,9 @@ class CatalogueGL {
8221
8346
  _providerUrl;
8222
8347
  _catalogueShaderProgram;
8223
8348
  _visibleTilesManager;
8349
+ _mediaOverlayHost = null;
8350
+ _mediaElements = [];
8351
+ _svgMediaDataUrls = new Map();
8224
8352
  constructor(catalogueName, catalogueDescription, providerUrl, metadataManager, webgl, visibleTilesManager) {
8225
8353
  this._webgl = webgl;
8226
8354
  this._ready = false;
@@ -8391,9 +8519,10 @@ class CatalogueGL {
8391
8519
  * @param in_data Rows of TAP results
8392
8520
  * @param columnsmeta TapMetadataList (unused here because `CatalogueProps` already holds indices)
8393
8521
  */
8394
- addSources(in_data, columnsmeta) {
8522
+ addSources(in_data, columnsmeta, options = {}) {
8395
8523
  this._ready = false;
8396
8524
  this._sources = [];
8525
+ this.clearMediaOverlay();
8397
8526
  this._metadataManager = new MetadataManager_js_1.MetadataManager(columnsmeta);
8398
8527
  // const raDataIndex = (this.catalogueProps.raColumn as any).index ?? (this.catalogueProps.raColumn as any)._index;
8399
8528
  // const decDataIndex = (this.catalogueProps.decColumn as any).index ?? (this.catalogueProps.decColumn as any)._index;
@@ -8407,6 +8536,10 @@ class CatalogueGL {
8407
8536
  decDeg: in_data[j][decDataIndex],
8408
8537
  }, CoordsType_js_1.CoordsType.ASTRO);
8409
8538
  const source = new Source_js_1.Source(point, in_data[j]);
8539
+ const mediaStyle = this.extractMediaStyle(in_data[j], columnsmeta, options.mediaColumns);
8540
+ if (mediaStyle) {
8541
+ source.mediaStyle = mediaStyle;
8542
+ }
8410
8543
  // Ensure optional fields exist
8411
8544
  source.shapeSize = source.shapeSize ?? CatalogueGL.STANDARD_SHAPE_SIZE;
8412
8545
  source.brightnessFactor = 3;
@@ -8426,11 +8559,222 @@ class CatalogueGL {
8426
8559
  this._ready = true;
8427
8560
  this._bufferInitialised = false;
8428
8561
  }
8562
+ getColumnIndex(columnsmeta, columnName) {
8563
+ if (!columnName)
8564
+ return -1;
8565
+ return columnsmeta.find((column) => column.name === columnName)?.index ?? -1;
8566
+ }
8567
+ readCell(row, columnsmeta, columnName) {
8568
+ const idx = this.getColumnIndex(columnsmeta, columnName);
8569
+ return idx >= 0 ? row[idx] : undefined;
8570
+ }
8571
+ extractMediaStyle(row, columnsmeta, mediaColumns) {
8572
+ if (!mediaColumns?.src)
8573
+ return undefined;
8574
+ const rawSrc = this.readCell(row, columnsmeta, mediaColumns.src);
8575
+ const src = String(rawSrc ?? "").trim();
8576
+ if (!src)
8577
+ return undefined;
8578
+ const rawKind = String(this.readCell(row, columnsmeta, mediaColumns.type) ?? "sprite").trim().toLowerCase();
8579
+ const kind = rawKind === "model"
8580
+ ? "model"
8581
+ : rawKind === "image"
8582
+ ? "image"
8583
+ : rawKind === "icon"
8584
+ ? "icon"
8585
+ : rawKind === "circle"
8586
+ ? "circle"
8587
+ : "sprite";
8588
+ if (kind === "circle" || kind === "model")
8589
+ return undefined;
8590
+ const scale = Number(this.readCell(row, columnsmeta, mediaColumns.scale));
8591
+ const rotationDeg = Number(this.readCell(row, columnsmeta, mediaColumns.rotation));
8592
+ const opacity = Number(this.readCell(row, columnsmeta, mediaColumns.opacity));
8593
+ return {
8594
+ kind,
8595
+ src,
8596
+ scale: Number.isFinite(scale) && scale > 0 ? scale : undefined,
8597
+ rotationDeg: Number.isFinite(rotationDeg) ? rotationDeg : undefined,
8598
+ opacity: Number.isFinite(opacity) ? Math.max(0, Math.min(1, opacity)) : undefined,
8599
+ };
8600
+ }
8429
8601
  clearSources() {
8430
8602
  this._sources = [];
8431
8603
  this.hoveredIndexes = [];
8432
8604
  this._healpixDensityMap.clear();
8433
8605
  this.vertexCataloguePosition = new Float32Array(0);
8606
+ this.clearMediaOverlay();
8607
+ }
8608
+ clearMediaOverlay() {
8609
+ this._mediaElements.forEach((el) => el.remove());
8610
+ this._mediaElements = [];
8611
+ if (this._mediaOverlayHost) {
8612
+ this._mediaOverlayHost.remove();
8613
+ this._mediaOverlayHost = null;
8614
+ }
8615
+ }
8616
+ ensureMediaOverlayHost() {
8617
+ const canvas = this._webgl?.canvas;
8618
+ const parent = canvas?.parentElement;
8619
+ if (!canvas || !parent)
8620
+ return null;
8621
+ const parentStyle = window.getComputedStyle(parent);
8622
+ if (parentStyle.position === "static") {
8623
+ parent.style.position = "relative";
8624
+ }
8625
+ if (!this._mediaOverlayHost) {
8626
+ const host = document.createElement("div");
8627
+ host.dataset.astroCatalogueMedia = "true";
8628
+ host.style.position = "absolute";
8629
+ host.style.left = `${canvas.offsetLeft}px`;
8630
+ host.style.top = `${canvas.offsetTop}px`;
8631
+ host.style.width = `${canvas.clientWidth}px`;
8632
+ host.style.height = `${canvas.clientHeight}px`;
8633
+ host.style.pointerEvents = "none";
8634
+ host.style.overflow = "hidden";
8635
+ host.style.zIndex = "4";
8636
+ canvas.style.position = canvas.style.position || "relative";
8637
+ canvas.style.zIndex = canvas.style.zIndex || "0";
8638
+ parent.appendChild(host);
8639
+ this._mediaOverlayHost = host;
8640
+ }
8641
+ else {
8642
+ this._mediaOverlayHost.style.left = `${canvas.offsetLeft}px`;
8643
+ this._mediaOverlayHost.style.top = `${canvas.offsetTop}px`;
8644
+ this._mediaOverlayHost.style.width = `${canvas.clientWidth}px`;
8645
+ this._mediaOverlayHost.style.height = `${canvas.clientHeight}px`;
8646
+ }
8647
+ return this._mediaOverlayHost;
8648
+ }
8649
+ ensureMediaElement(index, style) {
8650
+ let img = this._mediaElements[index];
8651
+ if (!img) {
8652
+ img = document.createElement("img");
8653
+ img.loading = "lazy";
8654
+ img.decoding = "async";
8655
+ img.style.position = "absolute";
8656
+ img.style.objectFit = "contain";
8657
+ img.style.pointerEvents = "none";
8658
+ img.style.transformOrigin = "center center";
8659
+ img.style.filter = "drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55))";
8660
+ img.style.willChange = "transform, left, top";
8661
+ this._mediaElements[index] = img;
8662
+ }
8663
+ const src = String(style.src ?? "").trim();
8664
+ const resolvedSrc = src ? new URL(src, document.baseURI).href : "";
8665
+ if (resolvedSrc && img.getAttribute("src") !== resolvedSrc) {
8666
+ img.removeAttribute("data-astro-media-src-index");
8667
+ img.removeAttribute("data-astro-media-loaded");
8668
+ img.onerror = null;
8669
+ img.onload = null;
8670
+ img.setAttribute("src", resolvedSrc);
8671
+ if (/\.svg(?:[?#].*)?$/i.test(resolvedSrc)) {
8672
+ void this.loadSvgMediaAsDataUrl(img, resolvedSrc);
8673
+ }
8674
+ }
8675
+ return img;
8676
+ }
8677
+ async loadSvgMediaAsDataUrl(img, resolvedSrc) {
8678
+ try {
8679
+ let dataUrl = this._svgMediaDataUrls.get(resolvedSrc);
8680
+ if (!dataUrl) {
8681
+ const response = await fetch(resolvedSrc);
8682
+ if (!response.ok)
8683
+ return;
8684
+ const svg = await response.text();
8685
+ if (!/^\s*<svg[\s>]/i.test(svg))
8686
+ return;
8687
+ dataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
8688
+ this._svgMediaDataUrls.set(resolvedSrc, dataUrl);
8689
+ }
8690
+ if (img.getAttribute("src") === resolvedSrc) {
8691
+ img.setAttribute("src", dataUrl);
8692
+ }
8693
+ }
8694
+ catch {
8695
+ // Keep the direct image src; remote servers may already provide a valid MIME type.
8696
+ }
8697
+ }
8698
+ isSourceOnVisibleHemisphere(source, in_mMatrix, vMatrix) {
8699
+ if (Global_js_1.default.insideSphere)
8700
+ return true;
8701
+ const invView = gl_matrix_1.mat4.create();
8702
+ if (!gl_matrix_1.mat4.invert(invView, vMatrix))
8703
+ return true;
8704
+ const cameraPos = gl_matrix_1.vec3.fromValues(invView[12], invView[13], invView[14]);
8705
+ const worldPoint4 = gl_matrix_1.vec4.fromValues(source.point.x, source.point.y, source.point.z, 1);
8706
+ gl_matrix_1.vec4.transformMat4(worldPoint4, worldPoint4, in_mMatrix);
8707
+ const worldPoint = gl_matrix_1.vec3.fromValues(worldPoint4[0], worldPoint4[1], worldPoint4[2]);
8708
+ const normal = gl_matrix_1.vec3.normalize(gl_matrix_1.vec3.create(), worldPoint);
8709
+ const toCamera = gl_matrix_1.vec3.subtract(gl_matrix_1.vec3.create(), cameraPos, worldPoint);
8710
+ return gl_matrix_1.vec3.dot(normal, toCamera) > 0;
8711
+ }
8712
+ updateMediaOverlay(in_mMatrix, vMatrix, pMatrix) {
8713
+ const sourcesWithMedia = this._sources.filter((source) => !!source.mediaStyle?.src);
8714
+ if (!sourcesWithMedia.length) {
8715
+ this.clearMediaOverlay();
8716
+ return;
8717
+ }
8718
+ const host = this.ensureMediaOverlayHost();
8719
+ const canvas = this._webgl.canvas;
8720
+ if (!host || !canvas.clientWidth || !canvas.clientHeight)
8721
+ return;
8722
+ const modelView = gl_matrix_1.mat4.create();
8723
+ const mvp = gl_matrix_1.mat4.create();
8724
+ gl_matrix_1.mat4.multiply(modelView, vMatrix, in_mMatrix);
8725
+ gl_matrix_1.mat4.multiply(mvp, pMatrix, modelView);
8726
+ const width = canvas.clientWidth;
8727
+ const height = canvas.clientHeight;
8728
+ for (let i = 0; i < this._sources.length; i++) {
8729
+ const source = this._sources[i];
8730
+ const style = source.mediaStyle;
8731
+ const img = this._mediaElements[i];
8732
+ if (!style?.src) {
8733
+ if (img)
8734
+ img.style.display = "none";
8735
+ continue;
8736
+ }
8737
+ if (!this.isSourceOnVisibleHemisphere(source, in_mMatrix, vMatrix)) {
8738
+ if (img)
8739
+ img.style.display = "none";
8740
+ continue;
8741
+ }
8742
+ const projected = gl_matrix_1.vec4.fromValues(source.point.x, source.point.y, source.point.z, 1);
8743
+ gl_matrix_1.vec4.transformMat4(projected, projected, mvp);
8744
+ if (!projected[3]) {
8745
+ if (img)
8746
+ img.style.display = "none";
8747
+ continue;
8748
+ }
8749
+ const ndcX = projected[0] / projected[3];
8750
+ const ndcY = projected[1] / projected[3];
8751
+ const ndcZ = projected[2] / projected[3];
8752
+ if (ndcX < -1.15 ||
8753
+ ndcX > 1.15 ||
8754
+ ndcY < -1.15 ||
8755
+ ndcY > 1.15 ||
8756
+ ndcZ < -1.15 ||
8757
+ ndcZ > 1.15) {
8758
+ if (img)
8759
+ img.style.display = "none";
8760
+ continue;
8761
+ }
8762
+ const mediaEl = this.ensureMediaElement(i, style);
8763
+ if (!mediaEl.parentElement)
8764
+ host.appendChild(mediaEl);
8765
+ const px = (ndcX * 0.5 + 0.5) * width;
8766
+ const py = (1 - (ndcY * 0.5 + 0.5)) * height;
8767
+ const baseSize = source.shapeSize || CatalogueGL.STANDARD_SHAPE_SIZE;
8768
+ const size = Math.max(8, baseSize * (style.scale ?? 2.4));
8769
+ const rotation = style.rotationDeg ?? 0;
8770
+ mediaEl.style.display = "block";
8771
+ mediaEl.style.left = `${px - size / 2}px`;
8772
+ mediaEl.style.top = `${py - size / 2}px`;
8773
+ mediaEl.style.width = `${size}px`;
8774
+ mediaEl.style.height = `${size}px`;
8775
+ mediaEl.style.opacity = `${style.opacity ?? 1}`;
8776
+ mediaEl.style.transform = `rotate(${rotation}deg)`;
8777
+ }
8434
8778
  }
8435
8779
  sourceMatches(left, right) {
8436
8780
  if (left === right)
@@ -8770,6 +9114,7 @@ class CatalogueGL {
8770
9114
  // draw
8771
9115
  const numItems = this.vertexCataloguePosition.length / CatalogueGL.ELEM_SIZE;
8772
9116
  this._webgl.drawArrays(this._webgl.POINTS, 0, numItems);
9117
+ this.updateMediaOverlay(in_mMatrix, vMatrix, pMatrix);
8773
9118
  this._oldMouseCoords = in_mouseHelper.xyz;
8774
9119
  }
8775
9120
  }