maplibre-gl-layers 0.10.0 → 0.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.
package/dist/index.mjs CHANGED
@@ -3,12 +3,12 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  /*!
5
5
  * name: maplibre-gl-layers
6
- * version: 0.10.0
6
+ * version: 0.11.0
7
7
  * description: MapLibre's layer extension library enabling the display, movement, and modification of large numbers of dynamic sprite images
8
8
  * author: Kouji Matsui (@kekyo@mi.kekyo.net)
9
9
  * license: MIT
10
10
  * repository.url: https://github.com/kekyo/maplibre-gl-layers.git
11
- * git.commit.hash: 58b99588e56fc80c6874d78a17e91a50901abc17
11
+ * git.commit.hash: 371efb126f281333d59ec75cfe788d45f3b1482e
12
12
  */
13
13
  const UNLIMITED_SPRITE_SCALING_OPTIONS = {
14
14
  metersPerPixel: 1,
@@ -21934,13 +21934,6 @@ const applySurfaceDisplacement = (baseLng, baseLat, east, north) => {
21934
21934
  lat: baseLat + deltaLat
21935
21935
  };
21936
21936
  };
21937
- const screenToClip = (x, y, drawingBufferWidth, drawingBufferHeight, pixelRatio) => {
21938
- const deviceX = x * pixelRatio;
21939
- const deviceY = y * pixelRatio;
21940
- const clipX = deviceX / drawingBufferWidth * 2 - 1;
21941
- const clipY = 1 - deviceY / drawingBufferHeight * 2;
21942
- return [clipX, clipY];
21943
- };
21944
21937
  const clipToScreen = (clipPosition, drawingBufferWidth, drawingBufferHeight, pixelRatio) => {
21945
21938
  const [clipX, clipY, , clipW] = clipPosition;
21946
21939
  if (!Number.isFinite(clipW) || clipW === 0) {
@@ -22085,41 +22078,55 @@ const calculateBillboardCenterPosition = (params) => {
22085
22078
  offsetShift
22086
22079
  };
22087
22080
  };
22088
- const BILLBOARD_BASE_CORNERS = [
22089
- [-1, 1],
22090
- [1, 1],
22091
- [-1, -1],
22092
- [1, -1]
22093
- ];
22094
- const calculateBillboardCornerScreenPositions = (params) => {
22081
+ const computeSurfaceCornerShaderModel = (params) => {
22095
22082
  var _a, _b;
22096
- const { centerX, centerY, halfWidth, halfHeight, anchor, totalRotateDeg } = params;
22083
+ const {
22084
+ baseLngLat,
22085
+ worldWidthMeters,
22086
+ worldHeightMeters,
22087
+ anchor,
22088
+ totalRotateDeg,
22089
+ offsetMeters
22090
+ } = params;
22091
+ const halfWidth = worldWidthMeters / 2;
22092
+ const halfHeight = worldHeightMeters / 2;
22097
22093
  if (halfWidth <= 0 || halfHeight <= 0) {
22098
- return UV_CORNERS.map(([u, v]) => ({ x: centerX, y: centerY, u, v }));
22094
+ const cosLat2 = Math.cos(baseLngLat.lat * DEG2RAD);
22095
+ const cosLatClamped2 = Math.max(cosLat2, MIN_COS_LAT);
22096
+ const deltaLat = offsetMeters.north / EARTH_RADIUS_METERS * RAD2DEG;
22097
+ const deltaLng = offsetMeters.east / (EARTH_RADIUS_METERS * cosLatClamped2) * RAD2DEG;
22098
+ return SURFACE_BASE_CORNERS$1.map(() => ({
22099
+ east: offsetMeters.east,
22100
+ north: offsetMeters.north,
22101
+ lng: baseLngLat.lng + deltaLng,
22102
+ lat: baseLngLat.lat + deltaLat
22103
+ }));
22099
22104
  }
22100
- const anchorOffsetX = ((_a = anchor == null ? void 0 : anchor.x) != null ? _a : 0) * halfWidth;
22101
- const anchorOffsetY = ((_b = anchor == null ? void 0 : anchor.y) != null ? _b : 0) * halfHeight;
22105
+ const anchorEast = ((_a = anchor == null ? void 0 : anchor.x) != null ? _a : 0) * halfWidth;
22106
+ const anchorNorth = ((_b = anchor == null ? void 0 : anchor.y) != null ? _b : 0) * halfHeight;
22102
22107
  const rad = -totalRotateDeg * DEG2RAD;
22103
- const cosR = Math.cos(rad);
22104
22108
  const sinR = Math.sin(rad);
22105
- const corners = [];
22106
- for (let i = 0; i < BILLBOARD_BASE_CORNERS.length; i++) {
22107
- const [cornerXNorm, cornerYNorm] = BILLBOARD_BASE_CORNERS[i];
22108
- const [u, v] = UV_CORNERS[i];
22109
- const cornerX = cornerXNorm * halfWidth;
22110
- const cornerY = cornerYNorm * halfHeight;
22111
- const shiftedX = cornerX - anchorOffsetX;
22112
- const shiftedY = cornerY - anchorOffsetY;
22113
- const rotatedX = shiftedX * cosR - shiftedY * sinR;
22114
- const rotatedY = shiftedX * sinR + shiftedY * cosR;
22115
- corners.push({
22116
- x: centerX + rotatedX,
22117
- y: centerY - rotatedY,
22118
- u,
22119
- v
22120
- });
22121
- }
22122
- return corners;
22109
+ const cosR = Math.cos(rad);
22110
+ const cosLat = Math.cos(baseLngLat.lat * DEG2RAD);
22111
+ const cosLatClamped = Math.max(cosLat, MIN_COS_LAT);
22112
+ return SURFACE_BASE_CORNERS$1.map(([eastNorm, northNorm]) => {
22113
+ const cornerEast = eastNorm * halfWidth;
22114
+ const cornerNorth = northNorm * halfHeight;
22115
+ const localEast = cornerEast - anchorEast;
22116
+ const localNorth = cornerNorth - anchorNorth;
22117
+ const rotatedEast = localEast * cosR - localNorth * sinR;
22118
+ const rotatedNorth = localEast * sinR + localNorth * cosR;
22119
+ const east = rotatedEast + offsetMeters.east;
22120
+ const north = rotatedNorth + offsetMeters.north;
22121
+ const deltaLat = north / EARTH_RADIUS_METERS * RAD2DEG;
22122
+ const deltaLng = east / (EARTH_RADIUS_METERS * cosLatClamped) * RAD2DEG;
22123
+ return {
22124
+ east,
22125
+ north,
22126
+ lng: baseLngLat.lng + deltaLng,
22127
+ lat: baseLngLat.lat + deltaLat
22128
+ };
22129
+ });
22123
22130
  };
22124
22131
  const calculateSurfaceCenterPosition = (params) => {
22125
22132
  var _a, _b;
@@ -22229,7 +22236,7 @@ const calculateSurfaceCenterPosition = (params) => {
22229
22236
  anchorlessLngLat
22230
22237
  };
22231
22238
  };
22232
- const SURFACE_BASE_CORNERS = [
22239
+ const SURFACE_BASE_CORNERS$1 = [
22233
22240
  [-1, 1],
22234
22241
  [1, 1],
22235
22242
  [-1, -1],
@@ -22245,7 +22252,7 @@ const calculateSurfaceCornerDisplacements = (params) => {
22245
22252
  offsetMeters
22246
22253
  } = params;
22247
22254
  if (worldWidthMeters <= 0 || worldHeightMeters <= 0) {
22248
- return SURFACE_BASE_CORNERS.map(() => ({
22255
+ return SURFACE_BASE_CORNERS$1.map(() => ({
22249
22256
  east: offsetMeters.east,
22250
22257
  north: offsetMeters.north
22251
22258
  }));
@@ -22258,7 +22265,7 @@ const calculateSurfaceCornerDisplacements = (params) => {
22258
22265
  const cosR = Math.cos(rad);
22259
22266
  const sinR = Math.sin(rad);
22260
22267
  const corners = [];
22261
- for (const [eastNorm, northNorm] of SURFACE_BASE_CORNERS) {
22268
+ for (const [eastNorm, northNorm] of SURFACE_BASE_CORNERS$1) {
22262
22269
  const cornerEast = eastNorm * halfWidth;
22263
22270
  const cornerNorth = northNorm * halfHeight;
22264
22271
  const localEast = cornerEast - anchorEast;
@@ -22509,12 +22516,262 @@ const applyOffsetUpdate = (image, nextOffset, options = {}) => {
22509
22516
  applyOffsetDegUpdate(image, nextOffset, options.deg);
22510
22517
  applyOffsetMetersUpdate(image, nextOffset, options.meters);
22511
22518
  };
22519
+ const DEFAULT_MAX_ITEMS_PER_NODE = 16;
22520
+ const DEFAULT_MAX_DEPTH = 8;
22521
+ const DEFAULT_LOOSENESS = 1.5;
22522
+ const createNode = (bounds, looseness, depth) => ({
22523
+ bounds,
22524
+ looseBounds: expandRect(bounds, looseness),
22525
+ items: [],
22526
+ children: null,
22527
+ depth
22528
+ });
22529
+ const normalizeRect = (rect) => {
22530
+ const x0 = Math.min(rect.x0, rect.x1);
22531
+ const y0 = Math.min(rect.y0, rect.y1);
22532
+ const x1 = Math.max(rect.x0, rect.x1);
22533
+ const y1 = Math.max(rect.y0, rect.y1);
22534
+ return { x0, y0, x1, y1 };
22535
+ };
22536
+ const isFiniteRect = (rect) => Number.isFinite(rect.x0) && Number.isFinite(rect.y0) && Number.isFinite(rect.x1) && Number.isFinite(rect.y1);
22537
+ const expandRect = (rect, looseness) => {
22538
+ if (looseness === 1) {
22539
+ return rect;
22540
+ }
22541
+ const width = rect.x1 - rect.x0;
22542
+ const height = rect.y1 - rect.y0;
22543
+ const halfWidth = width * looseness / 2;
22544
+ const halfHeight = height * looseness / 2;
22545
+ const centerX = rect.x0 + width / 2;
22546
+ const centerY = rect.y0 + height / 2;
22547
+ return {
22548
+ x0: centerX - halfWidth,
22549
+ y0: centerY - halfHeight,
22550
+ x1: centerX + halfWidth,
22551
+ y1: centerY + halfHeight
22552
+ };
22553
+ };
22554
+ const rectContainsRectInclusive = (container, target) => container.x0 <= target.x0 && container.y0 <= target.y0 && container.x1 >= target.x1 && container.y1 >= target.y1;
22555
+ const rectsOverlapInclusive = (a, b) => !(a.x1 < b.x0 || a.x0 > b.x1 || a.y1 < b.y0 || a.y0 > b.y1);
22556
+ const rectEquals = (a, b) => a.x0 === b.x0 && a.y0 === b.y0 && a.x1 === b.x1 && a.y1 === b.y1;
22557
+ const createLooseQuadTree = (options) => {
22558
+ var _a, _b, _c;
22559
+ const maxItemsPerNode = (_a = options.maxItemsPerNode) != null ? _a : DEFAULT_MAX_ITEMS_PER_NODE;
22560
+ const maxDepth = (_b = options.maxDepth) != null ? _b : DEFAULT_MAX_DEPTH;
22561
+ const looseness = (_c = options.looseness) != null ? _c : DEFAULT_LOOSENESS;
22562
+ if (maxItemsPerNode <= 0) {
22563
+ throw new Error("maxItemsPerNode must be greater than 0.");
22564
+ }
22565
+ if (maxDepth < 0) {
22566
+ throw new Error("maxDepth must be 0 or greater.");
22567
+ }
22568
+ if (!(looseness >= 1)) {
22569
+ throw new Error("looseness must be greater than or equal to 1.");
22570
+ }
22571
+ const normalizedBounds = normalizeRect(options.bounds);
22572
+ if (!isFiniteRect(normalizedBounds)) {
22573
+ throw new Error("Bounds must have finite coordinates.");
22574
+ }
22575
+ let root = createNode(normalizedBounds, looseness, 0);
22576
+ let count = 0;
22577
+ let registry = /* @__PURE__ */ new WeakMap();
22578
+ const insertIntoNode = (node, quadItem) => {
22579
+ if (node.children) {
22580
+ const childIndex = findChildIndex(node.children, quadItem.rect);
22581
+ if (childIndex !== -1) {
22582
+ insertIntoNode(node.children[childIndex], quadItem);
22583
+ return;
22584
+ }
22585
+ }
22586
+ quadItem.node = node;
22587
+ quadItem.index = node.items.length;
22588
+ node.items.push(quadItem);
22589
+ if (node.items.length > maxItemsPerNode && node.depth < maxDepth) {
22590
+ if (!node.children) {
22591
+ subdivide(node);
22592
+ }
22593
+ const children = node.children;
22594
+ if (!children) {
22595
+ return;
22596
+ }
22597
+ let i = 0;
22598
+ while (i < node.items.length) {
22599
+ const candidate = node.items[i];
22600
+ const childIndex = findChildIndex(children, candidate.rect);
22601
+ if (childIndex !== -1) {
22602
+ detachFromNode(candidate);
22603
+ insertIntoNode(children[childIndex], candidate);
22604
+ } else {
22605
+ i += 1;
22606
+ }
22607
+ }
22608
+ }
22609
+ };
22610
+ const removeQuadItem = (quadItem) => {
22611
+ detachFromNode(quadItem);
22612
+ registry.delete(quadItem.item);
22613
+ };
22614
+ const detachFromNode = (quadItem) => {
22615
+ const node = quadItem.node;
22616
+ const index = quadItem.index;
22617
+ if (index < 0 || index >= node.items.length) {
22618
+ return;
22619
+ }
22620
+ const lastIndex = node.items.length - 1;
22621
+ if (index !== lastIndex) {
22622
+ const moved = node.items[lastIndex];
22623
+ node.items[index] = moved;
22624
+ moved.index = index;
22625
+ }
22626
+ node.items.pop();
22627
+ quadItem.index = -1;
22628
+ quadItem.node = node;
22629
+ };
22630
+ const reassignIfPossible = (quadItem) => {
22631
+ const node = quadItem.node;
22632
+ if (!node.children) {
22633
+ return;
22634
+ }
22635
+ const childIndex = findChildIndex(node.children, quadItem.rect);
22636
+ if (childIndex === -1) {
22637
+ return;
22638
+ }
22639
+ detachFromNode(quadItem);
22640
+ insertIntoNode(node.children[childIndex], quadItem);
22641
+ };
22642
+ const add = (item) => {
22643
+ const rect = normalizeRect(item);
22644
+ if (!rectContainsRectInclusive(root.bounds, rect)) {
22645
+ throw new Error("Item rectangle is outside of quadtree bounds.");
22646
+ }
22647
+ const quadItem = {
22648
+ item,
22649
+ rect,
22650
+ node: root,
22651
+ index: -1
22652
+ };
22653
+ insertIntoNode(root, quadItem);
22654
+ registry.set(item, quadItem);
22655
+ count += 1;
22656
+ };
22657
+ const remove = (x0, y0, x1, y1, item) => {
22658
+ const quadItem = registry.get(item);
22659
+ if (!quadItem) {
22660
+ return false;
22661
+ }
22662
+ const rect = normalizeRect({ x0, y0, x1, y1 });
22663
+ if (!rectEquals(rect, quadItem.rect)) {
22664
+ return false;
22665
+ }
22666
+ removeQuadItem(quadItem);
22667
+ count -= 1;
22668
+ return true;
22669
+ };
22670
+ const update = (oldX0, oldY0, oldX1, oldY1, newX0, newY0, newX1, newY1, item) => {
22671
+ const quadItem = registry.get(item);
22672
+ if (!quadItem) {
22673
+ return false;
22674
+ }
22675
+ const currentRect = quadItem.rect;
22676
+ const expectedOldRect = normalizeRect({
22677
+ x0: oldX0,
22678
+ y0: oldY0,
22679
+ x1: oldX1,
22680
+ y1: oldY1
22681
+ });
22682
+ if (!rectEquals(currentRect, expectedOldRect)) {
22683
+ return false;
22684
+ }
22685
+ const newRect = normalizeRect({
22686
+ x0: newX0,
22687
+ y0: newY0,
22688
+ x1: newX1,
22689
+ y1: newY1
22690
+ });
22691
+ if (!rectContainsRectInclusive(root.bounds, newRect)) {
22692
+ throw new Error("Updated rectangle is outside of quadtree bounds.");
22693
+ }
22694
+ if (rectContainsRectInclusive(quadItem.node.looseBounds, newRect)) {
22695
+ quadItem.rect = newRect;
22696
+ reassignIfPossible(quadItem);
22697
+ return true;
22698
+ }
22699
+ detachFromNode(quadItem);
22700
+ quadItem.rect = newRect;
22701
+ insertIntoNode(root, quadItem);
22702
+ return true;
22703
+ };
22704
+ const lookup = (x0, y0, x1, y1) => {
22705
+ const rect = normalizeRect({ x0, y0, x1, y1 });
22706
+ const results = [];
22707
+ collectFromNode(root, rect, results);
22708
+ return results;
22709
+ };
22710
+ const clear = () => {
22711
+ root = createNode(normalizedBounds, looseness, 0);
22712
+ registry = /* @__PURE__ */ new WeakMap();
22713
+ count = 0;
22714
+ };
22715
+ const collectFromNode = (node, rect, results) => {
22716
+ if (!rectsOverlapInclusive(node.looseBounds, rect)) {
22717
+ return;
22718
+ }
22719
+ for (const quadItem of node.items) {
22720
+ if (rectsOverlapInclusive(quadItem.rect, rect)) {
22721
+ results.push(quadItem.item);
22722
+ }
22723
+ }
22724
+ if (!node.children) {
22725
+ return;
22726
+ }
22727
+ for (const child of node.children) {
22728
+ collectFromNode(child, rect, results);
22729
+ }
22730
+ };
22731
+ const findChildIndex = (children, rect) => {
22732
+ for (let i = 0; i < children.length; i += 1) {
22733
+ const child = children[i];
22734
+ if (rectContainsRectInclusive(child.looseBounds, rect)) {
22735
+ return i;
22736
+ }
22737
+ }
22738
+ return -1;
22739
+ };
22740
+ const subdivide = (node) => {
22741
+ if (node.children) {
22742
+ return;
22743
+ }
22744
+ const { bounds } = node;
22745
+ const splitX = bounds.x0 + (bounds.x1 - bounds.x0) / 2;
22746
+ const splitY = bounds.y0 + (bounds.y1 - bounds.y0) / 2;
22747
+ const childrenBounds = [
22748
+ normalizeRect({ x0: bounds.x0, y0: bounds.y0, x1: splitX, y1: splitY }),
22749
+ normalizeRect({ x0: splitX, y0: bounds.y0, x1: bounds.x1, y1: splitY }),
22750
+ normalizeRect({ x0: bounds.x0, y0: splitY, x1: splitX, y1: bounds.y1 }),
22751
+ normalizeRect({ x0: splitX, y0: splitY, x1: bounds.x1, y1: bounds.y1 })
22752
+ ];
22753
+ node.children = childrenBounds.map(
22754
+ (childBounds) => createNode(childBounds, looseness, node.depth + 1)
22755
+ );
22756
+ };
22757
+ return {
22758
+ get size() {
22759
+ return count;
22760
+ },
22761
+ add,
22762
+ remove,
22763
+ update,
22764
+ lookup,
22765
+ clear
22766
+ };
22767
+ };
22512
22768
  const DEFAULT_ANCHOR = { x: 0, y: 0 };
22513
22769
  const DEFAULT_AUTO_ROTATION_MIN_DISTANCE_METERS = 20;
22514
22770
  const DEFAULT_IMAGE_OFFSET = {
22515
22771
  offsetMeters: 0,
22516
22772
  offsetDeg: 0
22517
22773
  };
22774
+ const HIT_TEST_QUERY_RADIUS_PIXELS = 32;
22518
22775
  const MIN_CLIP_W = 1e-6;
22519
22776
  const MIN_CLIP_Z_EPSILON = 1e-7;
22520
22777
  const EPS_NDC = 1e-6;
@@ -22605,7 +22862,7 @@ const resolveGlMagFilter = (glContext, filter) => {
22605
22862
  return glContext.LINEAR;
22606
22863
  }
22607
22864
  };
22608
- const calculatePerspectiveRatio = (mapInstance, location2) => {
22865
+ const calculatePerspectiveRatio = (mapInstance, location2, cachedMercator) => {
22609
22866
  var _a, _b, _c;
22610
22867
  const transform = mapInstance.transform;
22611
22868
  if (!transform) {
@@ -22617,7 +22874,7 @@ const calculatePerspectiveRatio = (mapInstance, location2) => {
22617
22874
  return 1;
22618
22875
  }
22619
22876
  try {
22620
- const mercator = maplibreGlExports.MercatorCoordinate.fromLngLat(
22877
+ const mercator = cachedMercator != null ? cachedMercator : maplibreGlExports.MercatorCoordinate.fromLngLat(
22621
22878
  { lng: location2.lng, lat: location2.lat },
22622
22879
  (_b = location2.z) != null ? _b : 0
22623
22880
  );
@@ -22750,10 +23007,59 @@ const QUAD_VERTEX_COUNT = 6;
22750
23007
  const VERTEX_SHADER_SOURCE = `
22751
23008
  attribute vec4 a_position;
22752
23009
  attribute vec2 a_uv;
23010
+ uniform vec2 u_screenToClipScale;
23011
+ uniform vec2 u_screenToClipOffset;
23012
+ uniform float u_billboardMode;
23013
+ uniform float u_surfaceMode;
23014
+ uniform vec2 u_billboardCenter;
23015
+ uniform vec2 u_billboardHalfSize;
23016
+ uniform vec2 u_billboardAnchor;
23017
+ uniform vec2 u_billboardSinCos;
23018
+ uniform float u_surfaceClipEnabled;
23019
+ uniform vec4 u_surfaceClipCenter;
23020
+ uniform vec4 u_surfaceClipBasisEast;
23021
+ uniform vec4 u_surfaceClipBasisNorth;
23022
+ uniform float u_surfaceDepthBias;
22753
23023
  varying vec2 v_uv;
23024
+ vec2 computeBillboardCorner(vec2 uv) {
23025
+ vec2 base = vec2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
23026
+ vec2 anchorShift = vec2(u_billboardAnchor.x * u_billboardHalfSize.x, u_billboardAnchor.y * u_billboardHalfSize.y);
23027
+ vec2 shifted = vec2(base.x * u_billboardHalfSize.x, base.y * u_billboardHalfSize.y) - anchorShift;
23028
+ float sinR = u_billboardSinCos.x;
23029
+ float cosR = u_billboardSinCos.y;
23030
+ vec2 rotated = vec2(
23031
+ shifted.x * cosR - shifted.y * sinR,
23032
+ shifted.x * sinR + shifted.y * cosR
23033
+ );
23034
+ return vec2(
23035
+ u_billboardCenter.x + rotated.x,
23036
+ u_billboardCenter.y - rotated.y
23037
+ );
23038
+ }
23039
+ vec4 computeSurfaceCorner(vec2 corner) {
23040
+ if (u_surfaceClipEnabled < 0.5) {
23041
+ return vec4(0.0, 0.0, 0.0, 1.0);
23042
+ }
23043
+ vec4 clip = u_surfaceClipCenter
23044
+ + (corner.x * u_surfaceClipBasisEast)
23045
+ + (corner.y * u_surfaceClipBasisNorth);
23046
+ clip.z += u_surfaceDepthBias * clip.w;
23047
+ return clip;
23048
+ }
22754
23049
  void main() {
22755
23050
  v_uv = a_uv;
22756
- gl_Position = a_position;
23051
+ vec4 position;
23052
+ if (u_billboardMode > 0.5) {
23053
+ vec2 screenPosition = computeBillboardCorner(a_uv);
23054
+ position = vec4(screenPosition, 0.0, 1.0);
23055
+ } else if (u_surfaceMode > 0.5) {
23056
+ vec2 baseCorner = vec2(a_position.x, a_position.y);
23057
+ position = computeSurfaceCorner(baseCorner);
23058
+ } else {
23059
+ position = a_position;
23060
+ }
23061
+ position.xy = position.xy * u_screenToClipScale + u_screenToClipOffset;
23062
+ gl_Position = position;
22757
23063
  }
22758
23064
  `;
22759
23065
  const FRAGMENT_SHADER_SOURCE = `
@@ -22772,6 +23078,175 @@ const INITIAL_QUAD_VERTICES = new Float32Array(
22772
23078
  const QUAD_VERTEX_SCRATCH = new Float32Array(
22773
23079
  QUAD_VERTEX_COUNT * VERTEX_COMPONENT_COUNT
22774
23080
  );
23081
+ const DEBUG_OUTLINE_VERTEX_SHADER_SOURCE = `
23082
+ attribute vec4 a_position;
23083
+ uniform vec2 u_screenToClipScale;
23084
+ uniform vec2 u_screenToClipOffset;
23085
+ void main() {
23086
+ vec4 position = a_position;
23087
+ position.xy = position.xy * u_screenToClipScale + u_screenToClipOffset;
23088
+ gl_Position = position;
23089
+ }
23090
+ `;
23091
+ const DEBUG_OUTLINE_FRAGMENT_SHADER_SOURCE = `
23092
+ precision mediump float;
23093
+ uniform vec4 u_color;
23094
+ void main() {
23095
+ gl_FragColor = u_color;
23096
+ }
23097
+ `;
23098
+ const DEBUG_OUTLINE_VERTEX_COUNT = 4;
23099
+ const DEBUG_OUTLINE_POSITION_COMPONENT_COUNT = 4;
23100
+ const DEBUG_OUTLINE_VERTEX_STRIDE = DEBUG_OUTLINE_POSITION_COMPONENT_COUNT * FLOAT_SIZE;
23101
+ const DEBUG_OUTLINE_VERTEX_SCRATCH = new Float32Array(
23102
+ DEBUG_OUTLINE_VERTEX_COUNT * DEBUG_OUTLINE_POSITION_COMPONENT_COUNT
23103
+ );
23104
+ const DEBUG_OUTLINE_COLOR = [
23105
+ 1,
23106
+ 0,
23107
+ 0,
23108
+ 1
23109
+ ];
23110
+ const DEBUG_OUTLINE_CORNER_ORDER = [0, 1, 3, 2];
23111
+ const BILLBOARD_BASE_CORNERS = [
23112
+ [-1, 1],
23113
+ [1, 1],
23114
+ [-1, -1],
23115
+ [1, -1]
23116
+ ];
23117
+ const SURFACE_BASE_CORNERS = [
23118
+ [-1, 1],
23119
+ [1, 1],
23120
+ [-1, -1],
23121
+ [1, -1]
23122
+ ];
23123
+ const computeBillboardCornersShaderModel = ({
23124
+ centerX,
23125
+ centerY,
23126
+ halfWidth,
23127
+ halfHeight,
23128
+ anchor,
23129
+ rotationDeg
23130
+ }) => {
23131
+ var _a, _b;
23132
+ const anchorX = (_a = anchor == null ? void 0 : anchor.x) != null ? _a : 0;
23133
+ const anchorY = (_b = anchor == null ? void 0 : anchor.y) != null ? _b : 0;
23134
+ const rad = -rotationDeg * DEG2RAD;
23135
+ const cosR = Math.cos(rad);
23136
+ const sinR = Math.sin(rad);
23137
+ return BILLBOARD_BASE_CORNERS.map(([cornerXNorm, cornerYNorm], index) => {
23138
+ const cornerX = cornerXNorm * halfWidth;
23139
+ const cornerY = cornerYNorm * halfHeight;
23140
+ const shiftedX = cornerX - anchorX * halfWidth;
23141
+ const shiftedY = cornerY - anchorY * halfHeight;
23142
+ const rotatedX = shiftedX * cosR - shiftedY * sinR;
23143
+ const rotatedY = shiftedX * sinR + shiftedY * cosR;
23144
+ const [u, v] = UV_CORNERS[index];
23145
+ return {
23146
+ x: centerX + rotatedX,
23147
+ y: centerY - rotatedY,
23148
+ u,
23149
+ v
23150
+ };
23151
+ });
23152
+ };
23153
+ const calculateWorldToMercatorScale = (base, altitudeMeters) => {
23154
+ const origin = maplibreGlExports.MercatorCoordinate.fromLngLat(
23155
+ { lng: base.lng, lat: base.lat },
23156
+ altitudeMeters
23157
+ );
23158
+ const eastLngLat = applySurfaceDisplacement(base.lng, base.lat, 1, 0);
23159
+ const eastCoord = maplibreGlExports.MercatorCoordinate.fromLngLat(
23160
+ { lng: eastLngLat.lng, lat: eastLngLat.lat },
23161
+ altitudeMeters
23162
+ );
23163
+ const northLngLat = applySurfaceDisplacement(base.lng, base.lat, 0, 1);
23164
+ const northCoord = maplibreGlExports.MercatorCoordinate.fromLngLat(
23165
+ { lng: northLngLat.lng, lat: northLngLat.lat },
23166
+ altitudeMeters
23167
+ );
23168
+ return {
23169
+ east: eastCoord.x - origin.x,
23170
+ north: northCoord.y - origin.y
23171
+ };
23172
+ };
23173
+ const prepareSurfaceShaderInputs = (params) => {
23174
+ var _a, _b;
23175
+ const {
23176
+ baseLngLat,
23177
+ worldWidthMeters,
23178
+ worldHeightMeters,
23179
+ anchor,
23180
+ totalRotateDeg,
23181
+ offsetMeters,
23182
+ displacedCenter,
23183
+ altitudeMeters,
23184
+ depthBiasNdc,
23185
+ scaleAdjustment,
23186
+ centerDisplacement
23187
+ } = params;
23188
+ const halfSizeMeters = {
23189
+ east: worldWidthMeters / 2,
23190
+ north: worldHeightMeters / 2
23191
+ };
23192
+ const rotationRad = -totalRotateDeg * DEG2RAD;
23193
+ const sinR = Math.sin(rotationRad);
23194
+ const cosR = Math.cos(rotationRad);
23195
+ const mercatorCenter = maplibreGlExports.MercatorCoordinate.fromLngLat(
23196
+ { lng: displacedCenter.lng, lat: displacedCenter.lat },
23197
+ altitudeMeters
23198
+ );
23199
+ const worldToMercatorScale = calculateWorldToMercatorScale(
23200
+ displacedCenter,
23201
+ altitudeMeters
23202
+ );
23203
+ const cornerModel = computeSurfaceCornerShaderModel({
23204
+ baseLngLat,
23205
+ worldWidthMeters,
23206
+ worldHeightMeters,
23207
+ anchor,
23208
+ totalRotateDeg,
23209
+ offsetMeters
23210
+ });
23211
+ return {
23212
+ mercatorCenter: {
23213
+ x: mercatorCenter.x,
23214
+ y: mercatorCenter.y,
23215
+ z: (_a = mercatorCenter.z) != null ? _a : 0
23216
+ },
23217
+ worldToMercatorScale,
23218
+ halfSizeMeters,
23219
+ anchor,
23220
+ offsetMeters: {
23221
+ east: offsetMeters.east,
23222
+ north: offsetMeters.north
23223
+ },
23224
+ sinCos: { sin: sinR, cos: cosR },
23225
+ totalRotateDeg,
23226
+ depthBiasNdc,
23227
+ centerDisplacement: {
23228
+ east: centerDisplacement.east,
23229
+ north: centerDisplacement.north
23230
+ },
23231
+ baseLngLat,
23232
+ displacedCenter: {
23233
+ lng: displacedCenter.lng,
23234
+ lat: displacedCenter.lat,
23235
+ z: (_b = displacedCenter.z) != null ? _b : altitudeMeters
23236
+ },
23237
+ scaleAdjustment,
23238
+ corners: cornerModel.map((corner) => ({
23239
+ east: corner.east,
23240
+ north: corner.north,
23241
+ lng: corner.lng,
23242
+ lat: corner.lat
23243
+ })),
23244
+ clipCenter: { x: 0, y: 0, z: 0, w: 1 },
23245
+ clipBasisEast: { x: 0, y: 0, z: 0, w: 0 },
23246
+ clipBasisNorth: { x: 0, y: 0, z: 0, w: 0 },
23247
+ clipCorners: []
23248
+ };
23249
+ };
22775
23250
  const compileShader = (glContext, type, source) => {
22776
23251
  var _a;
22777
23252
  const shader = glContext.createShader(type);
@@ -23205,42 +23680,403 @@ const createImageStateFromInit = (imageInit, subLayer, order) => {
23205
23680
  lastCommandOffsetDeg: initialOffset.offsetDeg,
23206
23681
  lastCommandOffsetMeters: initialOffset.offsetMeters
23207
23682
  };
23208
- const rotateInitOption = (_i = (_h = imageInit.interpolation) == null ? void 0 : _h.rotateDeg) != null ? _i : null;
23209
- if (rotateInitOption) {
23210
- state.rotationInterpolationOptions = cloneInterpolationOptions(rotateInitOption);
23211
- }
23212
- syncImageRotationChannel(state);
23213
- return state;
23214
- };
23215
- const createSpriteLayer = (options) => {
23216
- var _a;
23217
- const id = (_a = options == null ? void 0 : options.id) != null ? _a : "sprite-layer";
23218
- const resolvedScaling = resolveScalingOptions(options == null ? void 0 : options.spriteScaling);
23219
- const resolvedTextureFiltering = resolveTextureFilteringOptions(
23220
- options == null ? void 0 : options.textureFiltering
23221
- );
23222
- let gl = null;
23223
- let map = null;
23224
- let program = null;
23225
- let vertexBuffer = null;
23226
- let attribPositionLocation = -1;
23227
- let attribUvLocation = -1;
23228
- let uniformTextureLocation = null;
23229
- let uniformOpacityLocation = null;
23230
- let anisotropyExtension = null;
23231
- let maxSupportedAnisotropy = 1;
23232
- const images = /* @__PURE__ */ new Map();
23233
- const queuedTextureIds = /* @__PURE__ */ new Set();
23234
- const queueTextureUpload = (image) => {
23235
- queuedTextureIds.add(image.id);
23683
+ const rotateInitOption = (_i = (_h = imageInit.interpolation) == null ? void 0 : _h.rotateDeg) != null ? _i : null;
23684
+ if (rotateInitOption) {
23685
+ state.rotationInterpolationOptions = cloneInterpolationOptions(rotateInitOption);
23686
+ }
23687
+ syncImageRotationChannel(state);
23688
+ return state;
23689
+ };
23690
+ const createSpriteLayer = (options) => {
23691
+ var _a;
23692
+ const id = (_a = options == null ? void 0 : options.id) != null ? _a : "sprite-layer";
23693
+ const resolvedScaling = resolveScalingOptions(options == null ? void 0 : options.spriteScaling);
23694
+ const resolvedTextureFiltering = resolveTextureFilteringOptions(
23695
+ options == null ? void 0 : options.textureFiltering
23696
+ );
23697
+ const showDebugBounds = (options == null ? void 0 : options.showDebugBounds) === true;
23698
+ let gl = null;
23699
+ let map = null;
23700
+ let program = null;
23701
+ let vertexBuffer = null;
23702
+ let attribPositionLocation = -1;
23703
+ let attribUvLocation = -1;
23704
+ let uniformTextureLocation = null;
23705
+ let uniformOpacityLocation = null;
23706
+ let uniformScreenToClipScaleLocation = null;
23707
+ let uniformScreenToClipOffsetLocation = null;
23708
+ let uniformBillboardModeLocation = null;
23709
+ let uniformBillboardCenterLocation = null;
23710
+ let uniformBillboardHalfSizeLocation = null;
23711
+ let uniformBillboardAnchorLocation = null;
23712
+ let uniformBillboardSinCosLocation = null;
23713
+ let uniformSurfaceModeLocation = null;
23714
+ let uniformSurfaceDepthBiasLocation = null;
23715
+ let uniformSurfaceClipEnabledLocation = null;
23716
+ let uniformSurfaceClipCenterLocation = null;
23717
+ let uniformSurfaceClipBasisEastLocation = null;
23718
+ let uniformSurfaceClipBasisNorthLocation = null;
23719
+ let anisotropyExtension = null;
23720
+ let maxSupportedAnisotropy = 1;
23721
+ let debugProgram = null;
23722
+ let debugVertexBuffer = null;
23723
+ let debugAttribPositionLocation = -1;
23724
+ let debugUniformColorLocation = null;
23725
+ let debugUniformScreenToClipScaleLocation = null;
23726
+ let debugUniformScreenToClipOffsetLocation = null;
23727
+ const images = /* @__PURE__ */ new Map();
23728
+ const queuedTextureIds = /* @__PURE__ */ new Set();
23729
+ const queueTextureUpload = (image) => {
23730
+ queuedTextureIds.add(image.id);
23731
+ };
23732
+ const cancelQueuedTextureUpload = (imageId) => {
23733
+ queuedTextureIds.delete(imageId);
23734
+ };
23735
+ const clearTextureQueue = () => {
23736
+ queuedTextureIds.clear();
23737
+ };
23738
+ const sprites = /* @__PURE__ */ new Map();
23739
+ const resolveSpriteMercator = (sprite) => {
23740
+ var _a2;
23741
+ const location2 = sprite.currentLocation;
23742
+ const altitude = (_a2 = location2.z) != null ? _a2 : 0;
23743
+ if (sprite.cachedMercator && sprite.cachedMercatorLng === location2.lng && sprite.cachedMercatorLat === location2.lat && sprite.cachedMercatorZ === altitude) {
23744
+ return sprite.cachedMercator;
23745
+ }
23746
+ const mercator = maplibreGlExports.MercatorCoordinate.fromLngLat(
23747
+ { lng: location2.lng, lat: location2.lat },
23748
+ altitude
23749
+ );
23750
+ sprite.cachedMercator = mercator;
23751
+ sprite.cachedMercatorLng = location2.lng;
23752
+ sprite.cachedMercatorLat = location2.lat;
23753
+ sprite.cachedMercatorZ = altitude;
23754
+ return mercator;
23755
+ };
23756
+ const HIT_TEST_WORLD_BOUNDS = {
23757
+ x0: -180,
23758
+ y0: -90,
23759
+ x1: 180,
23760
+ y1: 90
23761
+ };
23762
+ const hitTestTree = createLooseQuadTree({
23763
+ bounds: HIT_TEST_WORLD_BOUNDS
23764
+ });
23765
+ let hitTestTreeItems = /* @__PURE__ */ new WeakMap();
23766
+ let isHitTestEnabled = true;
23767
+ const rectFromLngLatPoints = (points) => {
23768
+ let minLng = Number.POSITIVE_INFINITY;
23769
+ let maxLng = Number.NEGATIVE_INFINITY;
23770
+ let minLat = Number.POSITIVE_INFINITY;
23771
+ let maxLat = Number.NEGATIVE_INFINITY;
23772
+ for (const point of points) {
23773
+ if (!point || !Number.isFinite(point.lng) || !Number.isFinite(point.lat)) {
23774
+ continue;
23775
+ }
23776
+ if (point.lng < minLng) minLng = point.lng;
23777
+ if (point.lng > maxLng) maxLng = point.lng;
23778
+ if (point.lat < minLat) minLat = point.lat;
23779
+ if (point.lat > maxLat) maxLat = point.lat;
23780
+ }
23781
+ if (minLng === Number.POSITIVE_INFINITY || maxLng === Number.NEGATIVE_INFINITY || minLat === Number.POSITIVE_INFINITY || maxLat === Number.NEGATIVE_INFINITY) {
23782
+ return null;
23783
+ }
23784
+ return {
23785
+ x0: Math.max(
23786
+ HIT_TEST_WORLD_BOUNDS.x0,
23787
+ Math.min(minLng, HIT_TEST_WORLD_BOUNDS.x1)
23788
+ ),
23789
+ y0: Math.max(
23790
+ HIT_TEST_WORLD_BOUNDS.y0,
23791
+ Math.min(minLat, HIT_TEST_WORLD_BOUNDS.y1)
23792
+ ),
23793
+ x1: Math.max(
23794
+ HIT_TEST_WORLD_BOUNDS.x0,
23795
+ Math.min(maxLng, HIT_TEST_WORLD_BOUNDS.x1)
23796
+ ),
23797
+ y1: Math.max(
23798
+ HIT_TEST_WORLD_BOUNDS.y0,
23799
+ Math.min(maxLat, HIT_TEST_WORLD_BOUNDS.y1)
23800
+ )
23801
+ };
23802
+ };
23803
+ const rectFromRadiusMeters = (base, radiusMeters) => {
23804
+ if (!Number.isFinite(base.lng) || !Number.isFinite(base.lat) || !Number.isFinite(radiusMeters) || radiusMeters <= 0) {
23805
+ return null;
23806
+ }
23807
+ const cornerNE = applySurfaceDisplacement(
23808
+ base.lng,
23809
+ base.lat,
23810
+ radiusMeters,
23811
+ radiusMeters
23812
+ );
23813
+ const cornerSW = applySurfaceDisplacement(
23814
+ base.lng,
23815
+ base.lat,
23816
+ -radiusMeters,
23817
+ -radiusMeters
23818
+ );
23819
+ return rectFromLngLatPoints([cornerNE, cornerSW]);
23820
+ };
23821
+ const estimateSurfaceImageBounds = (sprite, image) => {
23822
+ var _a2, _b, _c, _d, _e;
23823
+ const mapInstance = map;
23824
+ if (!mapInstance) {
23825
+ return null;
23826
+ }
23827
+ const imageResource = images.get(image.imageId);
23828
+ if (!imageResource) {
23829
+ return null;
23830
+ }
23831
+ const baseLocation = sprite.currentLocation;
23832
+ const zoom = mapInstance.getZoom();
23833
+ const zoomScaleFactor = calculateZoomScaleFactor(zoom, resolvedScaling);
23834
+ const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
23835
+ zoom,
23836
+ baseLocation.lat
23837
+ );
23838
+ if (!Number.isFinite(metersPerPixelAtLat) || metersPerPixelAtLat <= 0) {
23839
+ return null;
23840
+ }
23841
+ const spriteMercator = resolveSpriteMercator(sprite);
23842
+ const perspectiveRatio = calculatePerspectiveRatio(
23843
+ mapInstance,
23844
+ baseLocation,
23845
+ spriteMercator
23846
+ );
23847
+ const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
23848
+ metersPerPixelAtLat,
23849
+ perspectiveRatio
23850
+ );
23851
+ if (!Number.isFinite(effectivePixelsPerMeter) || effectivePixelsPerMeter <= 0) {
23852
+ return null;
23853
+ }
23854
+ const imageScale = (_a2 = image.scale) != null ? _a2 : 1;
23855
+ const baseMetersPerPixel = resolvedScaling.metersPerPixel;
23856
+ const spriteMinPixel = resolvedScaling.spriteMinPixel;
23857
+ const spriteMaxPixel = resolvedScaling.spriteMaxPixel;
23858
+ const worldDims = calculateSurfaceWorldDimensions(
23859
+ imageResource.width,
23860
+ imageResource.height,
23861
+ baseMetersPerPixel,
23862
+ imageScale,
23863
+ zoomScaleFactor,
23864
+ {
23865
+ effectivePixelsPerMeter,
23866
+ spriteMinPixel,
23867
+ spriteMaxPixel
23868
+ }
23869
+ );
23870
+ if (worldDims.width <= 0 || worldDims.height <= 0) {
23871
+ return null;
23872
+ }
23873
+ const anchor = (_b = image.anchor) != null ? _b : DEFAULT_ANCHOR;
23874
+ const offsetDef = (_c = image.offset) != null ? _c : DEFAULT_IMAGE_OFFSET;
23875
+ const offsetMetersVec = calculateSurfaceOffsetMeters(
23876
+ offsetDef,
23877
+ imageScale,
23878
+ zoomScaleFactor,
23879
+ worldDims.scaleAdjustment
23880
+ );
23881
+ const totalRotateDeg = Number.isFinite(image.displayedRotateDeg) ? image.displayedRotateDeg : normalizeAngleDeg(
23882
+ ((_d = image.resolvedBaseRotateDeg) != null ? _d : 0) + ((_e = image.rotateDeg) != null ? _e : 0)
23883
+ );
23884
+ const cornerDisplacements = calculateSurfaceCornerDisplacements({
23885
+ worldWidthMeters: worldDims.width,
23886
+ worldHeightMeters: worldDims.height,
23887
+ anchor,
23888
+ totalRotateDeg,
23889
+ offsetMeters: offsetMetersVec
23890
+ });
23891
+ const corners = cornerDisplacements.map(
23892
+ (corner) => applySurfaceDisplacement(
23893
+ baseLocation.lng,
23894
+ baseLocation.lat,
23895
+ corner.east,
23896
+ corner.north
23897
+ )
23898
+ );
23899
+ return rectFromLngLatPoints(corners);
23900
+ };
23901
+ const estimateBillboardImageBounds = (sprite, image) => {
23902
+ var _a2, _b, _c, _d;
23903
+ const mapInstance = map;
23904
+ if (!mapInstance) {
23905
+ return null;
23906
+ }
23907
+ const imageResource = images.get(image.imageId);
23908
+ if (!imageResource) {
23909
+ return null;
23910
+ }
23911
+ const baseLocation = sprite.currentLocation;
23912
+ const zoom = mapInstance.getZoom();
23913
+ const zoomScaleFactor = calculateZoomScaleFactor(zoom, resolvedScaling);
23914
+ const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
23915
+ zoom,
23916
+ baseLocation.lat
23917
+ );
23918
+ if (!Number.isFinite(metersPerPixelAtLat) || metersPerPixelAtLat <= 0) {
23919
+ return null;
23920
+ }
23921
+ const spriteMercator = resolveSpriteMercator(sprite);
23922
+ const perspectiveRatio = calculatePerspectiveRatio(
23923
+ mapInstance,
23924
+ baseLocation,
23925
+ spriteMercator
23926
+ );
23927
+ const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
23928
+ metersPerPixelAtLat,
23929
+ perspectiveRatio
23930
+ );
23931
+ if (!Number.isFinite(effectivePixelsPerMeter) || effectivePixelsPerMeter <= 0) {
23932
+ return null;
23933
+ }
23934
+ const baseMetersPerPixel = resolvedScaling.metersPerPixel;
23935
+ const spriteMinPixel = resolvedScaling.spriteMinPixel;
23936
+ const spriteMaxPixel = resolvedScaling.spriteMaxPixel;
23937
+ const imageScale = (_a2 = image.scale) != null ? _a2 : 1;
23938
+ const totalRotateDeg = Number.isFinite(image.displayedRotateDeg) ? image.displayedRotateDeg : normalizeAngleDeg(
23939
+ ((_b = image.resolvedBaseRotateDeg) != null ? _b : 0) + ((_c = image.rotateDeg) != null ? _c : 0)
23940
+ );
23941
+ const pixelDims = calculateBillboardPixelDimensions(
23942
+ imageResource.width,
23943
+ imageResource.height,
23944
+ baseMetersPerPixel,
23945
+ imageScale,
23946
+ zoomScaleFactor,
23947
+ effectivePixelsPerMeter,
23948
+ spriteMinPixel,
23949
+ spriteMaxPixel
23950
+ );
23951
+ const halfWidthMeters = pixelDims.width / 2 / effectivePixelsPerMeter;
23952
+ const halfHeightMeters = pixelDims.height / 2 / effectivePixelsPerMeter;
23953
+ const anchorShift = calculateBillboardAnchorShiftPixels(
23954
+ pixelDims.width / 2,
23955
+ pixelDims.height / 2,
23956
+ image.anchor,
23957
+ totalRotateDeg
23958
+ );
23959
+ const offsetShift = calculateBillboardOffsetPixels(
23960
+ (_d = image.offset) != null ? _d : DEFAULT_IMAGE_OFFSET,
23961
+ imageScale,
23962
+ zoomScaleFactor,
23963
+ effectivePixelsPerMeter
23964
+ );
23965
+ const anchorShiftMeters = Math.hypot(anchorShift.x, anchorShift.y) / effectivePixelsPerMeter;
23966
+ const offsetShiftMeters = Math.hypot(offsetShift.x, offsetShift.y) / effectivePixelsPerMeter;
23967
+ const safetyRadius = Math.hypot(halfWidthMeters, halfHeightMeters) + anchorShiftMeters + offsetShiftMeters;
23968
+ return rectFromRadiusMeters(baseLocation, safetyRadius);
23969
+ };
23970
+ const estimateImageBounds = (sprite, image) => {
23971
+ if (image.opacity <= 0 || !sprite.isEnabled) {
23972
+ return null;
23973
+ }
23974
+ if (image.mode === "surface") {
23975
+ return estimateSurfaceImageBounds(sprite, image);
23976
+ }
23977
+ return estimateBillboardImageBounds(sprite, image);
23978
+ };
23979
+ const removeImageBoundsFromHitTestTree = (image) => {
23980
+ const handle = hitTestTreeItems.get(image);
23981
+ if (!handle) {
23982
+ return;
23983
+ }
23984
+ hitTestTree.remove(
23985
+ handle.rect.x0,
23986
+ handle.rect.y0,
23987
+ handle.rect.x1,
23988
+ handle.rect.y1,
23989
+ handle.item
23990
+ );
23991
+ hitTestTreeItems.delete(image);
23992
+ };
23993
+ const setItemRect = (item, rect) => {
23994
+ const mutable = item;
23995
+ mutable.x0 = rect.x0;
23996
+ mutable.y0 = rect.y0;
23997
+ mutable.x1 = rect.x1;
23998
+ mutable.y1 = rect.y1;
23236
23999
  };
23237
- const cancelQueuedTextureUpload = (imageId) => {
23238
- queuedTextureIds.delete(imageId);
24000
+ const registerImageBoundsInHitTestTree = (sprite, image) => {
24001
+ const existingHandle = hitTestTreeItems.get(image);
24002
+ if (!isHitTestEnabled) {
24003
+ if (existingHandle) {
24004
+ removeImageBoundsFromHitTestTree(image);
24005
+ }
24006
+ return;
24007
+ }
24008
+ const rect = estimateImageBounds(sprite, image);
24009
+ if (!rect) {
24010
+ if (existingHandle) {
24011
+ removeImageBoundsFromHitTestTree(image);
24012
+ }
24013
+ return;
24014
+ }
24015
+ if (!existingHandle) {
24016
+ const handle = {
24017
+ rect,
24018
+ item: {
24019
+ x0: rect.x0,
24020
+ y0: rect.y0,
24021
+ x1: rect.x1,
24022
+ y1: rect.y1,
24023
+ state: {
24024
+ sprite,
24025
+ image,
24026
+ drawIndex: 0
24027
+ }
24028
+ }
24029
+ };
24030
+ hitTestTree.add(handle.item);
24031
+ hitTestTreeItems.set(image, handle);
24032
+ return;
24033
+ }
24034
+ const currentRect = existingHandle.rect;
24035
+ const unchanged = currentRect.x0 === rect.x0 && currentRect.y0 === rect.y0 && currentRect.x1 === rect.x1 && currentRect.y1 === rect.y1;
24036
+ if (unchanged) {
24037
+ return;
24038
+ }
24039
+ const updated = hitTestTree.update(
24040
+ currentRect.x0,
24041
+ currentRect.y0,
24042
+ currentRect.x1,
24043
+ currentRect.y1,
24044
+ rect.x0,
24045
+ rect.y0,
24046
+ rect.x1,
24047
+ rect.y1,
24048
+ existingHandle.item
24049
+ );
24050
+ if (updated) {
24051
+ existingHandle.rect = rect;
24052
+ setItemRect(existingHandle.item, rect);
24053
+ return;
24054
+ }
24055
+ removeImageBoundsFromHitTestTree(image);
24056
+ const newHandle = {
24057
+ rect,
24058
+ item: {
24059
+ x0: rect.x0,
24060
+ y0: rect.y0,
24061
+ x1: rect.x1,
24062
+ y1: rect.y1,
24063
+ state: {
24064
+ sprite,
24065
+ image,
24066
+ drawIndex: 0
24067
+ }
24068
+ }
24069
+ };
24070
+ hitTestTree.add(newHandle.item);
24071
+ hitTestTreeItems.set(image, newHandle);
23239
24072
  };
23240
- const clearTextureQueue = () => {
23241
- queuedTextureIds.clear();
24073
+ const refreshSpriteHitTestBounds = (sprite) => {
24074
+ sprite.images.forEach((orderMap) => {
24075
+ orderMap.forEach((image) => {
24076
+ registerImageBoundsInHitTestTree(sprite, image);
24077
+ });
24078
+ });
23242
24079
  };
23243
- const sprites = /* @__PURE__ */ new Map();
23244
24080
  const getImageState = (sprite, subLayer, order) => {
23245
24081
  var _a2;
23246
24082
  return (
@@ -23273,34 +24109,38 @@ const createSpriteLayer = (options) => {
23273
24109
  return deleted;
23274
24110
  };
23275
24111
  const HIT_TEST_EPSILON = 1e-3;
23276
- const pointInTriangle = (point, a, b, c) => {
23277
- const v0x = c.x - a.x;
23278
- const v0y = c.y - a.y;
23279
- const v1x = b.x - a.x;
23280
- const v1y = b.y - a.y;
23281
- const v2x = point.x - a.x;
23282
- const v2y = point.y - a.y;
23283
- const dot00 = v0x * v0x + v0y * v0y;
23284
- const dot01 = v0x * v1x + v0y * v1y;
23285
- const dot02 = v0x * v2x + v0y * v2y;
23286
- const dot11 = v1x * v1x + v1y * v1y;
23287
- const dot12 = v1x * v2x + v1y * v2y;
23288
- const denom = dot00 * dot11 - dot01 * dot01;
23289
- if (Math.abs(denom) < HIT_TEST_EPSILON) {
23290
- return false;
24112
+ const pointInRenderedQuad = (point, corners) => {
24113
+ let hasPositiveCross = false;
24114
+ let hasNegativeCross = false;
24115
+ for (let i = 0; i < DEBUG_OUTLINE_CORNER_ORDER.length; i++) {
24116
+ const currentIndex = DEBUG_OUTLINE_CORNER_ORDER[i];
24117
+ const nextIndex = DEBUG_OUTLINE_CORNER_ORDER[(i + 1) % DEBUG_OUTLINE_CORNER_ORDER.length];
24118
+ const a = corners[currentIndex];
24119
+ const b = corners[nextIndex];
24120
+ const edgeX = b.x - a.x;
24121
+ const edgeY = b.y - a.y;
24122
+ const pointX = point.x - a.x;
24123
+ const pointY = point.y - a.y;
24124
+ const cross = edgeX * pointY - edgeY * pointX;
24125
+ if (Math.abs(cross) <= HIT_TEST_EPSILON) {
24126
+ continue;
24127
+ }
24128
+ if (cross > 0) {
24129
+ hasPositiveCross = true;
24130
+ } else {
24131
+ hasNegativeCross = true;
24132
+ }
24133
+ if (hasPositiveCross && hasNegativeCross) {
24134
+ return false;
24135
+ }
23291
24136
  }
23292
- const invDenom = 1 / denom;
23293
- const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
23294
- const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
23295
- const w = 1 - u - v;
23296
- return u >= -HIT_TEST_EPSILON && v >= -HIT_TEST_EPSILON && w >= -HIT_TEST_EPSILON;
24137
+ return true;
23297
24138
  };
23298
- const pointInQuad = (point, corners) => pointInTriangle(point, corners[0], corners[1], corners[2]) || pointInTriangle(point, corners[0], corners[2], corners[3]);
23299
24139
  const isPointInsideHitEntry = (entry, point) => {
23300
24140
  if (point.x < entry.minX - HIT_TEST_EPSILON || point.x > entry.maxX + HIT_TEST_EPSILON || point.y < entry.minY - HIT_TEST_EPSILON || point.y > entry.maxY + HIT_TEST_EPSILON) {
23301
24141
  return false;
23302
24142
  }
23303
- return pointInQuad(point, entry.corners);
24143
+ return pointInRenderedQuad(point, entry.corners);
23304
24144
  };
23305
24145
  const buildSortedSubLayerBuckets = (entries) => {
23306
24146
  const buckets = /* @__PURE__ */ new Map();
@@ -23442,6 +24282,7 @@ const createSpriteLayer = (options) => {
23442
24282
  };
23443
24283
  const renderTargetEntries = [];
23444
24284
  const hitTestEntries = [];
24285
+ let hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
23445
24286
  const ensureHitTestCorners = (imageEntry) => {
23446
24287
  if (!imageEntry.hitTestCorners) {
23447
24288
  imageEntry.hitTestCorners = [
@@ -23453,7 +24294,10 @@ const createSpriteLayer = (options) => {
23453
24294
  }
23454
24295
  return imageEntry.hitTestCorners;
23455
24296
  };
23456
- const registerHitTestEntry = (spriteEntry, imageEntry, screenCorners) => {
24297
+ const registerHitTestEntry = (spriteEntry, imageEntry, screenCorners, drawIndex) => {
24298
+ if (!isHitTestEnabled) {
24299
+ return;
24300
+ }
23457
24301
  const corners = screenCorners;
23458
24302
  let minX = corners[0].x;
23459
24303
  let maxX = corners[0].x;
@@ -23466,7 +24310,7 @@ const createSpriteLayer = (options) => {
23466
24310
  if (corner.y < minY) minY = corner.y;
23467
24311
  if (corner.y > maxY) maxY = corner.y;
23468
24312
  }
23469
- hitTestEntries.push({
24313
+ const entry = {
23470
24314
  sprite: spriteEntry,
23471
24315
  image: imageEntry,
23472
24316
  corners,
@@ -23474,9 +24318,15 @@ const createSpriteLayer = (options) => {
23474
24318
  maxX,
23475
24319
  minY,
23476
24320
  maxY
23477
- });
24321
+ };
24322
+ hitTestEntries.push(entry);
24323
+ hitTestEntryByImage.set(imageEntry, entry);
24324
+ const handle = hitTestTreeItems.get(imageEntry);
24325
+ if (handle) {
24326
+ handle.item.state.drawIndex = drawIndex;
24327
+ }
23478
24328
  };
23479
- const findTopmostHitEntry = (point) => {
24329
+ const findTopmostHitEntryLinear = (point) => {
23480
24330
  for (let i = hitTestEntries.length - 1; i >= 0; i--) {
23481
24331
  const entry = hitTestEntries[i];
23482
24332
  if (isPointInsideHitEntry(entry, point)) {
@@ -23485,6 +24335,66 @@ const createSpriteLayer = (options) => {
23485
24335
  }
23486
24336
  return null;
23487
24337
  };
24338
+ const findTopmostHitEntry = (point) => {
24339
+ if (!isHitTestEnabled) {
24340
+ return null;
24341
+ }
24342
+ const mapInstance = map;
24343
+ if (!mapInstance) {
24344
+ return findTopmostHitEntryLinear(point);
24345
+ }
24346
+ const centerLngLat = mapInstance.unproject([point.x, point.y]);
24347
+ if (!centerLngLat) {
24348
+ return findTopmostHitEntryLinear(point);
24349
+ }
24350
+ const searchPoints = [
24351
+ { lng: centerLngLat.lng, lat: centerLngLat.lat }
24352
+ ];
24353
+ const radius = HIT_TEST_QUERY_RADIUS_PIXELS;
24354
+ const offsets = [
24355
+ [point.x - radius, point.y - radius],
24356
+ [point.x + radius, point.y - radius],
24357
+ [point.x - radius, point.y + radius],
24358
+ [point.x + radius, point.y + radius]
24359
+ ];
24360
+ for (const [x, y] of offsets) {
24361
+ const lngLat = mapInstance.unproject([x, y]);
24362
+ if (lngLat) {
24363
+ searchPoints.push({ lng: lngLat.lng, lat: lngLat.lat });
24364
+ }
24365
+ }
24366
+ const searchRect = rectFromLngLatPoints(searchPoints);
24367
+ if (!searchRect) {
24368
+ return findTopmostHitEntryLinear(point);
24369
+ }
24370
+ const candidates = hitTestTree.lookup(
24371
+ searchRect.x0,
24372
+ searchRect.y0,
24373
+ searchRect.x1,
24374
+ searchRect.y1
24375
+ );
24376
+ if (candidates.length === 0) {
24377
+ return findTopmostHitEntryLinear(point);
24378
+ }
24379
+ candidates.sort((a, b) => a.state.drawIndex - b.state.drawIndex);
24380
+ const seenImages = /* @__PURE__ */ new Set();
24381
+ for (let i = candidates.length - 1; i >= 0; i--) {
24382
+ const candidate = candidates[i];
24383
+ const image = candidate.state.image;
24384
+ if (seenImages.has(image)) {
24385
+ continue;
24386
+ }
24387
+ seenImages.add(image);
24388
+ const entry = hitTestEntryByImage.get(image);
24389
+ if (!entry) {
24390
+ continue;
24391
+ }
24392
+ if (isPointInsideHitEntry(entry, point)) {
24393
+ return entry;
24394
+ }
24395
+ }
24396
+ return findTopmostHitEntryLinear(point);
24397
+ };
23488
24398
  const eventListeners = /* @__PURE__ */ new Map();
23489
24399
  const getListenerSet = (type) => {
23490
24400
  let set = eventListeners.get(type);
@@ -23867,7 +24777,59 @@ const createSpriteLayer = (options) => {
23867
24777
  shaderProgram,
23868
24778
  "u_opacity"
23869
24779
  );
23870
- if (!uniformTextureLocation || !uniformOpacityLocation) {
24780
+ uniformScreenToClipScaleLocation = glContext.getUniformLocation(
24781
+ shaderProgram,
24782
+ "u_screenToClipScale"
24783
+ );
24784
+ uniformScreenToClipOffsetLocation = glContext.getUniformLocation(
24785
+ shaderProgram,
24786
+ "u_screenToClipOffset"
24787
+ );
24788
+ uniformBillboardModeLocation = glContext.getUniformLocation(
24789
+ shaderProgram,
24790
+ "u_billboardMode"
24791
+ );
24792
+ uniformBillboardCenterLocation = glContext.getUniformLocation(
24793
+ shaderProgram,
24794
+ "u_billboardCenter"
24795
+ );
24796
+ uniformBillboardHalfSizeLocation = glContext.getUniformLocation(
24797
+ shaderProgram,
24798
+ "u_billboardHalfSize"
24799
+ );
24800
+ uniformBillboardAnchorLocation = glContext.getUniformLocation(
24801
+ shaderProgram,
24802
+ "u_billboardAnchor"
24803
+ );
24804
+ uniformBillboardSinCosLocation = glContext.getUniformLocation(
24805
+ shaderProgram,
24806
+ "u_billboardSinCos"
24807
+ );
24808
+ uniformSurfaceModeLocation = glContext.getUniformLocation(
24809
+ shaderProgram,
24810
+ "u_surfaceMode"
24811
+ );
24812
+ uniformSurfaceDepthBiasLocation = glContext.getUniformLocation(
24813
+ shaderProgram,
24814
+ "u_surfaceDepthBias"
24815
+ );
24816
+ uniformSurfaceClipEnabledLocation = glContext.getUniformLocation(
24817
+ shaderProgram,
24818
+ "u_surfaceClipEnabled"
24819
+ );
24820
+ uniformSurfaceClipCenterLocation = glContext.getUniformLocation(
24821
+ shaderProgram,
24822
+ "u_surfaceClipCenter"
24823
+ );
24824
+ uniformSurfaceClipBasisEastLocation = glContext.getUniformLocation(
24825
+ shaderProgram,
24826
+ "u_surfaceClipBasisEast"
24827
+ );
24828
+ uniformSurfaceClipBasisNorthLocation = glContext.getUniformLocation(
24829
+ shaderProgram,
24830
+ "u_surfaceClipBasisNorth"
24831
+ );
24832
+ if (!uniformTextureLocation || !uniformOpacityLocation || !uniformScreenToClipScaleLocation || !uniformScreenToClipOffsetLocation || !uniformBillboardModeLocation || !uniformBillboardCenterLocation || !uniformBillboardHalfSizeLocation || !uniformBillboardAnchorLocation || !uniformBillboardSinCosLocation || !uniformSurfaceModeLocation || !uniformSurfaceDepthBiasLocation || !uniformSurfaceClipEnabledLocation || !uniformSurfaceClipCenterLocation || !uniformSurfaceClipBasisEastLocation || !uniformSurfaceClipBasisNorthLocation) {
23871
24833
  throw new Error("Failed to acquire uniform locations.");
23872
24834
  }
23873
24835
  glContext.enableVertexAttribArray(attribPositionLocation);
@@ -23890,7 +24852,80 @@ const createSpriteLayer = (options) => {
23890
24852
  );
23891
24853
  glContext.uniform1i(uniformTextureLocation, 0);
23892
24854
  glContext.uniform1f(uniformOpacityLocation, 1);
24855
+ glContext.uniform2f(uniformScreenToClipScaleLocation, 1, 1);
24856
+ glContext.uniform2f(uniformScreenToClipOffsetLocation, 0, 0);
24857
+ glContext.uniform1f(uniformSurfaceClipEnabledLocation, 0);
24858
+ glContext.uniform4f(uniformSurfaceClipCenterLocation, 0, 0, 0, 1);
24859
+ glContext.uniform4f(
24860
+ uniformSurfaceClipBasisEastLocation,
24861
+ 0,
24862
+ 0,
24863
+ 0,
24864
+ 0
24865
+ );
24866
+ glContext.uniform4f(
24867
+ uniformSurfaceClipBasisNorthLocation,
24868
+ 0,
24869
+ 0,
24870
+ 0,
24871
+ 0
24872
+ );
24873
+ glContext.uniform1f(uniformBillboardModeLocation, 0);
24874
+ glContext.uniform2f(uniformBillboardCenterLocation, 0, 0);
24875
+ glContext.uniform2f(uniformBillboardHalfSizeLocation, 0, 0);
24876
+ glContext.uniform2f(uniformBillboardAnchorLocation, 0, 0);
24877
+ glContext.uniform2f(uniformBillboardSinCosLocation, 0, 1);
24878
+ glContext.uniform1f(uniformSurfaceModeLocation, 0);
24879
+ glContext.uniform1f(uniformSurfaceDepthBiasLocation, 0);
23893
24880
  glContext.bindBuffer(glContext.ARRAY_BUFFER, null);
24881
+ if (showDebugBounds) {
24882
+ const debugShaderProgram = createShaderProgram(
24883
+ glContext,
24884
+ DEBUG_OUTLINE_VERTEX_SHADER_SOURCE,
24885
+ DEBUG_OUTLINE_FRAGMENT_SHADER_SOURCE
24886
+ );
24887
+ debugProgram = debugShaderProgram;
24888
+ debugAttribPositionLocation = glContext.getAttribLocation(
24889
+ debugShaderProgram,
24890
+ "a_position"
24891
+ );
24892
+ if (debugAttribPositionLocation === -1) {
24893
+ throw new Error("Failed to acquire debug attribute location.");
24894
+ }
24895
+ const colorLocation = glContext.getUniformLocation(
24896
+ debugShaderProgram,
24897
+ "u_color"
24898
+ );
24899
+ if (!colorLocation) {
24900
+ throw new Error("Failed to acquire debug color uniform.");
24901
+ }
24902
+ debugUniformColorLocation = colorLocation;
24903
+ debugUniformScreenToClipScaleLocation = glContext.getUniformLocation(
24904
+ debugShaderProgram,
24905
+ "u_screenToClipScale"
24906
+ );
24907
+ debugUniformScreenToClipOffsetLocation = glContext.getUniformLocation(
24908
+ debugShaderProgram,
24909
+ "u_screenToClipOffset"
24910
+ );
24911
+ if (!debugUniformScreenToClipScaleLocation || !debugUniformScreenToClipOffsetLocation) {
24912
+ throw new Error("Failed to acquire debug screen-to-clip uniforms.");
24913
+ }
24914
+ glContext.uniform2f(debugUniformScreenToClipScaleLocation, 1, 1);
24915
+ glContext.uniform2f(debugUniformScreenToClipOffsetLocation, 0, 0);
24916
+ const outlineBuffer = glContext.createBuffer();
24917
+ if (!outlineBuffer) {
24918
+ throw new Error("Failed to create debug vertex buffer.");
24919
+ }
24920
+ debugVertexBuffer = outlineBuffer;
24921
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, outlineBuffer);
24922
+ glContext.bufferData(
24923
+ glContext.ARRAY_BUFFER,
24924
+ DEBUG_OUTLINE_VERTEX_SCRATCH,
24925
+ glContext.DYNAMIC_DRAW
24926
+ );
24927
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, null);
24928
+ }
23894
24929
  scheduleRender();
23895
24930
  };
23896
24931
  const onRemove = () => {
@@ -23898,6 +24933,9 @@ const createSpriteLayer = (options) => {
23898
24933
  inputListenerDisposers.length = 0;
23899
24934
  canvasElement = null;
23900
24935
  hitTestEntries.length = 0;
24936
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24937
+ hitTestTree.clear();
24938
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
23901
24939
  const glContext = gl;
23902
24940
  if (glContext) {
23903
24941
  images.forEach((image) => {
@@ -23910,9 +24948,15 @@ const createSpriteLayer = (options) => {
23910
24948
  if (vertexBuffer) {
23911
24949
  glContext.deleteBuffer(vertexBuffer);
23912
24950
  }
24951
+ if (debugVertexBuffer) {
24952
+ glContext.deleteBuffer(debugVertexBuffer);
24953
+ }
23913
24954
  if (program) {
23914
24955
  glContext.deleteProgram(program);
23915
24956
  }
24957
+ if (debugProgram) {
24958
+ glContext.deleteProgram(debugProgram);
24959
+ }
23916
24960
  }
23917
24961
  eventListeners.forEach((set) => set.clear());
23918
24962
  eventListeners.clear();
@@ -23920,20 +24964,36 @@ const createSpriteLayer = (options) => {
23920
24964
  map = null;
23921
24965
  program = null;
23922
24966
  vertexBuffer = null;
24967
+ debugProgram = null;
24968
+ debugVertexBuffer = null;
23923
24969
  attribPositionLocation = -1;
23924
24970
  attribUvLocation = -1;
24971
+ debugAttribPositionLocation = -1;
23925
24972
  uniformTextureLocation = null;
23926
24973
  uniformOpacityLocation = null;
24974
+ uniformScreenToClipScaleLocation = null;
24975
+ uniformScreenToClipOffsetLocation = null;
24976
+ uniformBillboardModeLocation = null;
24977
+ uniformBillboardCenterLocation = null;
24978
+ uniformBillboardHalfSizeLocation = null;
24979
+ uniformBillboardAnchorLocation = null;
24980
+ uniformBillboardSinCosLocation = null;
24981
+ uniformSurfaceModeLocation = null;
24982
+ uniformSurfaceDepthBiasLocation = null;
24983
+ debugUniformColorLocation = null;
24984
+ debugUniformScreenToClipScaleLocation = null;
24985
+ debugUniformScreenToClipOffsetLocation = null;
23927
24986
  anisotropyExtension = null;
23928
24987
  maxSupportedAnisotropy = 1;
23929
24988
  };
23930
24989
  const render = (glContext, _options) => {
23931
24990
  hitTestEntries.length = 0;
24991
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
23932
24992
  const mapInstance = map;
23933
24993
  if (!mapInstance || !program || !vertexBuffer) {
23934
24994
  return;
23935
24995
  }
23936
- if (!uniformOpacityLocation || !uniformTextureLocation) {
24996
+ if (!uniformOpacityLocation || !uniformTextureLocation || !uniformScreenToClipScaleLocation || !uniformScreenToClipOffsetLocation) {
23937
24997
  return;
23938
24998
  }
23939
24999
  const timestamp = typeof performance !== "undefined" && typeof performance.now === "function" ? (
@@ -23985,6 +25045,17 @@ const createSpriteLayer = (options) => {
23985
25045
  const drawingBufferWidth = glContext.drawingBufferWidth;
23986
25046
  const drawingBufferHeight = glContext.drawingBufferHeight;
23987
25047
  const pixelRatio = drawingBufferWidth / cssWidth;
25048
+ if (drawingBufferWidth === 0 || drawingBufferHeight === 0) {
25049
+ return;
25050
+ }
25051
+ const screenToClipScaleX = 2 * pixelRatio / drawingBufferWidth;
25052
+ const screenToClipScaleY = -2 * pixelRatio / drawingBufferHeight;
25053
+ const screenToClipOffsetX = -1;
25054
+ const screenToClipOffsetY = 1;
25055
+ const identityScaleX = 1;
25056
+ const identityScaleY = 1;
25057
+ const identityOffsetX = 0;
25058
+ const identityOffsetY = 0;
23988
25059
  const zoom = mapInstance.getZoom();
23989
25060
  const zoomScaleFactor = calculateZoomScaleFactor(zoom, resolvedScaling);
23990
25061
  const baseMetersPerPixel = resolvedScaling.metersPerPixel;
@@ -24019,11 +25090,76 @@ const createSpriteLayer = (options) => {
24019
25090
  UV_OFFSET
24020
25091
  );
24021
25092
  glContext.uniform1i(uniformTextureLocation, 0);
25093
+ const screenToClipScaleLocation = uniformScreenToClipScaleLocation;
25094
+ const screenToClipOffsetLocation = uniformScreenToClipOffsetLocation;
25095
+ let currentScaleX = Number.NaN;
25096
+ let currentScaleY = Number.NaN;
25097
+ let currentOffsetX = Number.NaN;
25098
+ let currentOffsetY = Number.NaN;
25099
+ const applyScreenToClipUniforms = (scaleX, scaleY, offsetX, offsetY) => {
25100
+ if (scaleX !== currentScaleX || scaleY !== currentScaleY || offsetX !== currentOffsetX || offsetY !== currentOffsetY) {
25101
+ glContext.uniform2f(screenToClipScaleLocation, scaleX, scaleY);
25102
+ glContext.uniform2f(screenToClipOffsetLocation, offsetX, offsetY);
25103
+ currentScaleX = scaleX;
25104
+ currentScaleY = scaleY;
25105
+ currentOffsetX = offsetX;
25106
+ currentOffsetY = offsetY;
25107
+ }
25108
+ };
25109
+ let currentSurfaceMode = Number.NaN;
25110
+ const applySurfaceMode = (enabled) => {
25111
+ if (!uniformSurfaceModeLocation) {
25112
+ return;
25113
+ }
25114
+ const value = enabled ? 1 : 0;
25115
+ if (value !== currentSurfaceMode) {
25116
+ glContext.uniform1f(uniformSurfaceModeLocation, value);
25117
+ currentSurfaceMode = value;
25118
+ }
25119
+ };
25120
+ let currentSurfaceClipEnabled = Number.NaN;
25121
+ const applySurfaceClipUniforms = (enabled, inputs) => {
25122
+ if (!uniformSurfaceClipEnabledLocation || !uniformSurfaceClipCenterLocation || !uniformSurfaceClipBasisEastLocation || !uniformSurfaceClipBasisNorthLocation) {
25123
+ return;
25124
+ }
25125
+ const value = enabled ? 1 : 0;
25126
+ if (value !== currentSurfaceClipEnabled) {
25127
+ glContext.uniform1f(uniformSurfaceClipEnabledLocation, value);
25128
+ currentSurfaceClipEnabled = value;
25129
+ }
25130
+ const clipCenter = enabled && inputs ? inputs.clipCenter : { x: 0, y: 0, z: 0, w: 1 };
25131
+ glContext.uniform4f(
25132
+ uniformSurfaceClipCenterLocation,
25133
+ clipCenter.x,
25134
+ clipCenter.y,
25135
+ clipCenter.z,
25136
+ clipCenter.w
25137
+ );
25138
+ const clipBasisEast = enabled && inputs ? inputs.clipBasisEast : { x: 0, y: 0, z: 0, w: 0 };
25139
+ glContext.uniform4f(
25140
+ uniformSurfaceClipBasisEastLocation,
25141
+ clipBasisEast.x,
25142
+ clipBasisEast.y,
25143
+ clipBasisEast.z,
25144
+ clipBasisEast.w
25145
+ );
25146
+ const clipBasisNorth = enabled && inputs ? inputs.clipBasisNorth : { x: 0, y: 0, z: 0, w: 0 };
25147
+ glContext.uniform4f(
25148
+ uniformSurfaceClipBasisNorthLocation,
25149
+ clipBasisNorth.x,
25150
+ clipBasisNorth.y,
25151
+ clipBasisNorth.z,
25152
+ clipBasisNorth.w
25153
+ );
25154
+ };
25155
+ let drawOrderCounter = 0;
24022
25156
  const drawSpriteImage = (spriteEntry, imageEntry, imageResource, originCenterCache2) => {
24023
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
25157
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
24024
25158
  let screenCornerBuffer = null;
24025
25159
  const anchor = (_a2 = imageEntry.anchor) != null ? _a2 : DEFAULT_ANCHOR;
24026
25160
  const offsetDef = (_b = imageEntry.offset) != null ? _b : DEFAULT_IMAGE_OFFSET;
25161
+ applySurfaceMode(false);
25162
+ applySurfaceClipUniforms(false, null);
24027
25163
  const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normalizeAngleDeg(
24028
25164
  ((_c = imageEntry.resolvedBaseRotateDeg) != null ? _c : 0) + ((_d = imageEntry.rotateDeg) != null ? _d : 0)
24029
25165
  );
@@ -24031,6 +25167,7 @@ const createSpriteLayer = (options) => {
24031
25167
  if (!projected) {
24032
25168
  return;
24033
25169
  }
25170
+ const spriteMercator = resolveSpriteMercator(spriteEntry);
24034
25171
  const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
24035
25172
  zoom,
24036
25173
  spriteEntry.currentLocation.lat
@@ -24040,7 +25177,8 @@ const createSpriteLayer = (options) => {
24040
25177
  }
24041
25178
  const perspectiveRatio = calculatePerspectiveRatio(
24042
25179
  mapInstance,
24043
- spriteEntry.currentLocation
25180
+ spriteEntry.currentLocation,
25181
+ spriteMercator
24044
25182
  );
24045
25183
  const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
24046
25184
  metersPerPixelAtLat,
@@ -24050,6 +25188,7 @@ const createSpriteLayer = (options) => {
24050
25188
  return;
24051
25189
  }
24052
25190
  const imageScale = (_e = imageEntry.scale) != null ? _e : 1;
25191
+ const altitudeMeters = (_f = spriteEntry.currentLocation.z) != null ? _f : 0;
24053
25192
  const centerParams = {
24054
25193
  mapInstance,
24055
25194
  images,
@@ -24064,7 +25203,7 @@ const createSpriteLayer = (options) => {
24064
25203
  drawingBufferHeight,
24065
25204
  pixelRatio,
24066
25205
  clipContext,
24067
- altitudeMeters: (_f = spriteEntry.currentLocation.z) != null ? _f : 0
25206
+ altitudeMeters
24068
25207
  };
24069
25208
  let baseProjected = { x: projected.x, y: projected.y };
24070
25209
  if (imageEntry.originLocation !== void 0) {
@@ -24081,6 +25220,13 @@ const createSpriteLayer = (options) => {
24081
25220
  }
24082
25221
  }
24083
25222
  if (imageEntry.mode === "surface") {
25223
+ applyScreenToClipUniforms(
25224
+ identityScaleX,
25225
+ identityScaleY,
25226
+ identityOffsetX,
25227
+ identityOffsetY
25228
+ );
25229
+ imageEntry.surfaceShaderInputs = void 0;
24084
25230
  const baseLngLat = imageEntry.originLocation !== void 0 ? (
24085
25231
  // When an origin reference is set, reproject the cached screen point back to geographic space.
24086
25232
  mapInstance.unproject([
@@ -24108,7 +25254,7 @@ const createSpriteLayer = (options) => {
24108
25254
  drawingBufferWidth,
24109
25255
  drawingBufferHeight,
24110
25256
  pixelRatio,
24111
- altitudeMeters: (_i = spriteEntry.currentLocation.z) != null ? _i : 0,
25257
+ altitudeMeters,
24112
25258
  project: !clipContext ? (lngLat) => {
24113
25259
  const result = mapInstance.project(lngLat);
24114
25260
  return result ? { x: result.x, y: result.y } : null;
@@ -24117,6 +25263,9 @@ const createSpriteLayer = (options) => {
24117
25263
  if (!surfaceCenter.center) {
24118
25264
  return;
24119
25265
  }
25266
+ if (uniformBillboardModeLocation) {
25267
+ glContext.uniform1f(uniformBillboardModeLocation, 0);
25268
+ }
24120
25269
  const offsetMeters = calculateSurfaceOffsetMeters(
24121
25270
  offsetDef,
24122
25271
  imageScale,
@@ -24130,6 +25279,51 @@ const createSpriteLayer = (options) => {
24130
25279
  totalRotateDeg,
24131
25280
  offsetMeters
24132
25281
  });
25282
+ const orderIndex = Math.min(imageEntry.order, ORDER_MAX - 1);
25283
+ const depthBiasNdc = -((imageEntry.subLayer * ORDER_BUCKET + orderIndex) * EPS_NDC);
25284
+ const displacedCenterLngLat = (_i = surfaceCenter.displacedLngLat) != null ? _i : baseLngLat;
25285
+ const displacedCenter = {
25286
+ lng: displacedCenterLngLat.lng,
25287
+ lat: displacedCenterLngLat.lat,
25288
+ z: altitudeMeters
25289
+ };
25290
+ const surfaceShaderInputs = prepareSurfaceShaderInputs({
25291
+ baseLngLat,
25292
+ worldWidthMeters: surfaceCenter.worldDimensions.width,
25293
+ worldHeightMeters: surfaceCenter.worldDimensions.height,
25294
+ anchor,
25295
+ totalRotateDeg,
25296
+ offsetMeters,
25297
+ displacedCenter,
25298
+ altitudeMeters,
25299
+ depthBiasNdc,
25300
+ scaleAdjustment: surfaceCenter.worldDimensions.scaleAdjustment,
25301
+ centerDisplacement: surfaceCenter.totalDisplacement
25302
+ });
25303
+ imageEntry.surfaceShaderInputs = surfaceShaderInputs;
25304
+ let useShaderSurface = !!clipContext;
25305
+ let clipCornerPositions = null;
25306
+ let clipCenterPosition = null;
25307
+ if (useShaderSurface) {
25308
+ clipCornerPositions = new Array(SURFACE_BASE_CORNERS.length);
25309
+ clipCenterPosition = projectLngLatToClipSpace(
25310
+ displacedCenter.lng,
25311
+ displacedCenter.lat,
25312
+ (_j = displacedCenter.z) != null ? _j : altitudeMeters,
25313
+ clipContext
25314
+ );
25315
+ if (!clipCenterPosition) {
25316
+ useShaderSurface = false;
25317
+ clipCornerPositions = null;
25318
+ }
25319
+ }
25320
+ applySurfaceMode(useShaderSurface);
25321
+ if (useShaderSurface && uniformSurfaceDepthBiasLocation) {
25322
+ glContext.uniform1f(
25323
+ uniformSurfaceDepthBiasLocation,
25324
+ surfaceShaderInputs.depthBiasNdc
25325
+ );
25326
+ }
24133
25327
  const hitTestCorners = ensureHitTestCorners(imageEntry);
24134
25328
  let bufferOffset = 0;
24135
25329
  for (const index of TRIANGLE_INDICES) {
@@ -24143,46 +25337,145 @@ const createSpriteLayer = (options) => {
24143
25337
  const clipPosition = projectLngLatToClipSpace(
24144
25338
  displaced.lng,
24145
25339
  displaced.lat,
24146
- // Default altitude to zero when sprites lack explicit elevation.
24147
- (_j = spriteEntry.currentLocation.z) != null ? _j : 0,
25340
+ altitudeMeters,
24148
25341
  clipContext
24149
25342
  );
24150
25343
  if (!clipPosition) {
24151
25344
  return;
24152
25345
  }
24153
- const screenCorner = clipToScreen(
24154
- clipPosition,
24155
- drawingBufferWidth,
24156
- drawingBufferHeight,
24157
- pixelRatio
24158
- );
24159
- if (!screenCorner) {
24160
- return;
24161
- }
24162
- const targetCorner = hitTestCorners[index];
24163
- targetCorner.x = screenCorner.x;
24164
- targetCorner.y = screenCorner.y;
24165
25346
  let [clipX, clipY, clipZ, clipW] = clipPosition;
24166
- {
24167
- const orderIndex = Math.min(imageEntry.order, ORDER_MAX - 1);
24168
- const biasIndex = imageEntry.subLayer * ORDER_BUCKET + orderIndex;
24169
- const biasNdc = -(biasIndex * EPS_NDC);
24170
- clipZ += biasNdc * clipW;
25347
+ if (!useShaderSurface) {
25348
+ const screenCorner = clipToScreen(
25349
+ clipPosition,
25350
+ drawingBufferWidth,
25351
+ drawingBufferHeight,
25352
+ pixelRatio
25353
+ );
25354
+ if (!screenCorner) {
25355
+ return;
25356
+ }
25357
+ const targetCorner = hitTestCorners[index];
25358
+ targetCorner.x = screenCorner.x;
25359
+ targetCorner.y = screenCorner.y;
25360
+ }
25361
+ if (depthBiasNdc !== 0) {
25362
+ clipZ += depthBiasNdc * clipW;
24171
25363
  const minClipZ = -clipW + MIN_CLIP_Z_EPSILON;
24172
25364
  if (clipZ < minClipZ) {
24173
25365
  clipZ = minClipZ;
24174
25366
  }
24175
25367
  }
25368
+ if (clipCornerPositions) {
25369
+ clipCornerPositions[index] = [clipX, clipY, clipZ, clipW];
25370
+ }
24176
25371
  const [u, v] = UV_CORNERS[index];
24177
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipX;
24178
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipY;
24179
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipZ;
24180
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipW;
25372
+ if (useShaderSurface) {
25373
+ const baseCorner = SURFACE_BASE_CORNERS[index];
25374
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = baseCorner[0];
25375
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = baseCorner[1];
25376
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = 0;
25377
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = 1;
25378
+ } else {
25379
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = clipX;
25380
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = clipY;
25381
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = clipZ;
25382
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = clipW;
25383
+ }
24181
25384
  QUAD_VERTEX_SCRATCH[bufferOffset++] = u;
24182
25385
  QUAD_VERTEX_SCRATCH[bufferOffset++] = v;
24183
25386
  }
25387
+ let clipUniformEnabled = false;
25388
+ if (clipCornerPositions && clipCenterPosition && clipCornerPositions.every((corner) => Array.isArray(corner))) {
25389
+ const leftTop = clipCornerPositions[0];
25390
+ const rightTop = clipCornerPositions[1];
25391
+ const leftBottom = clipCornerPositions[2];
25392
+ const rightBottom = clipCornerPositions[3];
25393
+ if (leftTop && rightTop && leftBottom && rightBottom) {
25394
+ const clipBasisEast = [
25395
+ (rightTop[0] - leftTop[0]) * 0.5,
25396
+ (rightTop[1] - leftTop[1]) * 0.5,
25397
+ (rightTop[2] - leftTop[2]) * 0.5,
25398
+ (rightTop[3] - leftTop[3]) * 0.5
25399
+ ];
25400
+ const clipBasisNorth = [
25401
+ (leftTop[0] - leftBottom[0]) * 0.5,
25402
+ (leftTop[1] - leftBottom[1]) * 0.5,
25403
+ (leftTop[2] - leftBottom[2]) * 0.5,
25404
+ (leftTop[3] - leftBottom[3]) * 0.5
25405
+ ];
25406
+ surfaceShaderInputs.clipCenter = {
25407
+ x: clipCenterPosition[0],
25408
+ y: clipCenterPosition[1],
25409
+ z: clipCenterPosition[2],
25410
+ w: clipCenterPosition[3]
25411
+ };
25412
+ surfaceShaderInputs.clipBasisEast = {
25413
+ x: clipBasisEast[0],
25414
+ y: clipBasisEast[1],
25415
+ z: clipBasisEast[2],
25416
+ w: clipBasisEast[3]
25417
+ };
25418
+ surfaceShaderInputs.clipBasisNorth = {
25419
+ x: clipBasisNorth[0],
25420
+ y: clipBasisNorth[1],
25421
+ z: clipBasisNorth[2],
25422
+ w: clipBasisNorth[3]
25423
+ };
25424
+ const clipCornersForInputs = [];
25425
+ let allCornersResolved = true;
25426
+ for (let cornerIndex = 0; cornerIndex < SURFACE_BASE_CORNERS.length; cornerIndex++) {
25427
+ const clipCorner = clipCornerPositions[cornerIndex];
25428
+ if (!clipCorner) {
25429
+ allCornersResolved = false;
25430
+ break;
25431
+ }
25432
+ clipCornersForInputs.push({
25433
+ x: clipCorner[0],
25434
+ y: clipCorner[1],
25435
+ z: clipCorner[2],
25436
+ w: clipCorner[3]
25437
+ });
25438
+ const screenCorner = clipToScreen(
25439
+ clipCorner,
25440
+ drawingBufferWidth,
25441
+ drawingBufferHeight,
25442
+ pixelRatio
25443
+ );
25444
+ if (!screenCorner) {
25445
+ return;
25446
+ }
25447
+ const targetCorner = hitTestCorners[cornerIndex];
25448
+ targetCorner.x = screenCorner.x;
25449
+ targetCorner.y = screenCorner.y;
25450
+ }
25451
+ if (allCornersResolved) {
25452
+ surfaceShaderInputs.clipCorners = clipCornersForInputs;
25453
+ clipUniformEnabled = true;
25454
+ } else {
25455
+ surfaceShaderInputs.clipCorners = [];
25456
+ }
25457
+ }
25458
+ } else {
25459
+ surfaceShaderInputs.clipCorners = [];
25460
+ }
25461
+ if (useShaderSurface) {
25462
+ applySurfaceClipUniforms(
25463
+ clipUniformEnabled,
25464
+ clipUniformEnabled ? surfaceShaderInputs : null
25465
+ );
25466
+ } else {
25467
+ applySurfaceClipUniforms(false, null);
25468
+ }
24184
25469
  screenCornerBuffer = hitTestCorners;
24185
25470
  } else {
25471
+ applyScreenToClipUniforms(
25472
+ screenToClipScaleX,
25473
+ screenToClipScaleY,
25474
+ screenToClipOffsetX,
25475
+ screenToClipOffsetY
25476
+ );
25477
+ imageEntry.surfaceShaderInputs = void 0;
25478
+ applySurfaceMode(false);
24186
25479
  const placement = calculateBillboardCenterPosition({
24187
25480
  base: baseProjected,
24188
25481
  imageWidth: imageResource.width,
@@ -24197,46 +25490,95 @@ const createSpriteLayer = (options) => {
24197
25490
  anchor,
24198
25491
  offset: offsetDef
24199
25492
  });
24200
- const corners = calculateBillboardCornerScreenPositions({
25493
+ const billboardShaderInputs = {
24201
25494
  centerX: placement.centerX,
24202
25495
  centerY: placement.centerY,
24203
25496
  halfWidth: placement.halfWidth,
24204
25497
  halfHeight: placement.halfHeight,
24205
25498
  anchor,
24206
25499
  totalRotateDeg
24207
- });
24208
- const hitTestCorners = ensureHitTestCorners(imageEntry);
24209
- let bufferOffset = 0;
24210
- for (const index of TRIANGLE_INDICES) {
24211
- const corner = corners[index];
24212
- const [clipX, clipY] = screenToClip(
24213
- corner.x,
24214
- corner.y,
24215
- drawingBufferWidth,
24216
- drawingBufferHeight,
24217
- pixelRatio
25500
+ };
25501
+ if (uniformBillboardModeLocation) {
25502
+ glContext.uniform1f(
25503
+ uniformBillboardModeLocation,
25504
+ 1
24218
25505
  );
24219
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipX;
24220
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipY;
24221
- QUAD_VERTEX_SCRATCH[bufferOffset++] = 0;
24222
- QUAD_VERTEX_SCRATCH[bufferOffset++] = 1;
24223
- QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.u;
24224
- QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.v;
24225
- }
24226
- for (let i = 0; i < corners.length; i++) {
24227
- const source = corners[i];
24228
- const target = hitTestCorners[i];
24229
- target.x = source.x;
24230
- target.y = source.y;
24231
25506
  }
24232
- screenCornerBuffer = hitTestCorners;
25507
+ const writeBillboardCorners = (corners, useShaderGeometry) => {
25508
+ const hitTestCorners = ensureHitTestCorners(imageEntry);
25509
+ let bufferOffset = 0;
25510
+ for (const index of TRIANGLE_INDICES) {
25511
+ const corner = corners[index];
25512
+ {
25513
+ const baseCorner = BILLBOARD_BASE_CORNERS[index];
25514
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = baseCorner[0];
25515
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = baseCorner[1];
25516
+ }
25517
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = 0;
25518
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = 1;
25519
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.u;
25520
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.v;
25521
+ }
25522
+ for (let i = 0; i < corners.length; i++) {
25523
+ const source = corners[i];
25524
+ const target = hitTestCorners[i];
25525
+ target.x = source.x;
25526
+ target.y = source.y;
25527
+ }
25528
+ screenCornerBuffer = hitTestCorners;
25529
+ };
25530
+ let resolvedCorners;
25531
+ let shaderModelCorners;
25532
+ {
25533
+ if (uniformBillboardCenterLocation) {
25534
+ glContext.uniform2f(
25535
+ uniformBillboardCenterLocation,
25536
+ billboardShaderInputs.centerX,
25537
+ billboardShaderInputs.centerY
25538
+ );
25539
+ }
25540
+ if (uniformBillboardHalfSizeLocation) {
25541
+ glContext.uniform2f(
25542
+ uniformBillboardHalfSizeLocation,
25543
+ billboardShaderInputs.halfWidth,
25544
+ billboardShaderInputs.halfHeight
25545
+ );
25546
+ }
25547
+ if (uniformBillboardAnchorLocation) {
25548
+ glContext.uniform2f(
25549
+ uniformBillboardAnchorLocation,
25550
+ (_l = (_k = billboardShaderInputs.anchor) == null ? void 0 : _k.x) != null ? _l : 0,
25551
+ (_n = (_m = billboardShaderInputs.anchor) == null ? void 0 : _m.y) != null ? _n : 0
25552
+ );
25553
+ }
25554
+ if (uniformBillboardSinCosLocation) {
25555
+ const rad = -billboardShaderInputs.totalRotateDeg * DEG2RAD;
25556
+ glContext.uniform2f(
25557
+ uniformBillboardSinCosLocation,
25558
+ Math.sin(rad),
25559
+ Math.cos(rad)
25560
+ );
25561
+ }
25562
+ shaderModelCorners = computeBillboardCornersShaderModel({
25563
+ centerX: billboardShaderInputs.centerX,
25564
+ centerY: billboardShaderInputs.centerY,
25565
+ halfWidth: billboardShaderInputs.halfWidth,
25566
+ halfHeight: billboardShaderInputs.halfHeight,
25567
+ anchor: billboardShaderInputs.anchor,
25568
+ rotationDeg: billboardShaderInputs.totalRotateDeg
25569
+ });
25570
+ resolvedCorners = shaderModelCorners;
25571
+ }
25572
+ writeBillboardCorners(resolvedCorners);
24233
25573
  }
24234
25574
  if (screenCornerBuffer && screenCornerBuffer.length === 4) {
24235
25575
  registerHitTestEntry(
24236
25576
  spriteEntry,
24237
25577
  imageEntry,
24238
- screenCornerBuffer
25578
+ screenCornerBuffer,
25579
+ drawOrderCounter
24239
25580
  );
25581
+ drawOrderCounter += 1;
24240
25582
  }
24241
25583
  glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, QUAD_VERTEX_SCRATCH);
24242
25584
  glContext.uniform1f(uniformOpacityLocation, imageEntry.opacity);
@@ -24266,6 +25608,7 @@ const createSpriteLayer = (options) => {
24266
25608
  if (!projected) {
24267
25609
  continue;
24268
25610
  }
25611
+ const spriteMercator = resolveSpriteMercator(spriteEntry);
24269
25612
  const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
24270
25613
  zoom,
24271
25614
  spriteEntry.currentLocation.lat
@@ -24275,7 +25618,8 @@ const createSpriteLayer = (options) => {
24275
25618
  }
24276
25619
  const perspectiveRatio = calculatePerspectiveRatio(
24277
25620
  mapInstance,
24278
- spriteEntry.currentLocation
25621
+ spriteEntry.currentLocation,
25622
+ spriteMercator
24279
25623
  );
24280
25624
  const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
24281
25625
  metersPerPixelAtLat,
@@ -24438,6 +25782,64 @@ const createSpriteLayer = (options) => {
24438
25782
  for (const [, bucket] of sortedSubLayerBuckets) {
24439
25783
  renderSortedBucket(bucket);
24440
25784
  }
25785
+ if (showDebugBounds && debugProgram && debugVertexBuffer && debugUniformColorLocation && debugAttribPositionLocation !== -1) {
25786
+ glContext.useProgram(debugProgram);
25787
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, debugVertexBuffer);
25788
+ glContext.enableVertexAttribArray(debugAttribPositionLocation);
25789
+ glContext.vertexAttribPointer(
25790
+ debugAttribPositionLocation,
25791
+ DEBUG_OUTLINE_POSITION_COMPONENT_COUNT,
25792
+ glContext.FLOAT,
25793
+ false,
25794
+ DEBUG_OUTLINE_VERTEX_STRIDE,
25795
+ 0
25796
+ );
25797
+ glContext.disable(glContext.DEPTH_TEST);
25798
+ glContext.depthMask(false);
25799
+ glContext.uniform4f(
25800
+ debugUniformColorLocation,
25801
+ DEBUG_OUTLINE_COLOR[0],
25802
+ DEBUG_OUTLINE_COLOR[1],
25803
+ DEBUG_OUTLINE_COLOR[2],
25804
+ DEBUG_OUTLINE_COLOR[3]
25805
+ );
25806
+ if (debugUniformScreenToClipScaleLocation && debugUniformScreenToClipOffsetLocation) {
25807
+ glContext.uniform2f(
25808
+ debugUniformScreenToClipScaleLocation,
25809
+ screenToClipScaleX,
25810
+ screenToClipScaleY
25811
+ );
25812
+ glContext.uniform2f(
25813
+ debugUniformScreenToClipOffsetLocation,
25814
+ screenToClipOffsetX,
25815
+ screenToClipOffsetY
25816
+ );
25817
+ }
25818
+ for (const entry of hitTestEntries) {
25819
+ let writeOffset = 0;
25820
+ for (const cornerIndex of DEBUG_OUTLINE_CORNER_ORDER) {
25821
+ const corner = entry.corners[cornerIndex];
25822
+ DEBUG_OUTLINE_VERTEX_SCRATCH[writeOffset++] = corner.x;
25823
+ DEBUG_OUTLINE_VERTEX_SCRATCH[writeOffset++] = corner.y;
25824
+ DEBUG_OUTLINE_VERTEX_SCRATCH[writeOffset++] = 0;
25825
+ DEBUG_OUTLINE_VERTEX_SCRATCH[writeOffset++] = 1;
25826
+ }
25827
+ glContext.bufferSubData(
25828
+ glContext.ARRAY_BUFFER,
25829
+ 0,
25830
+ DEBUG_OUTLINE_VERTEX_SCRATCH
25831
+ );
25832
+ glContext.drawArrays(
25833
+ glContext.LINE_LOOP,
25834
+ 0,
25835
+ DEBUG_OUTLINE_VERTEX_COUNT
25836
+ );
25837
+ }
25838
+ glContext.depthMask(true);
25839
+ glContext.enable(glContext.DEPTH_TEST);
25840
+ glContext.disableVertexAttribArray(debugAttribPositionLocation);
25841
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, null);
25842
+ }
24441
25843
  glContext.depthMask(true);
24442
25844
  glContext.enable(glContext.DEPTH_TEST);
24443
25845
  glContext.disable(glContext.BLEND);
@@ -24673,6 +26075,15 @@ const createSpriteLayer = (options) => {
24673
26075
  if (!image) {
24674
26076
  return false;
24675
26077
  }
26078
+ sprites.forEach((sprite) => {
26079
+ sprite.images.forEach((orderMap) => {
26080
+ orderMap.forEach((imageState) => {
26081
+ if (imageState.imageId === imageId) {
26082
+ removeImageBoundsFromHitTestTree(imageState);
26083
+ }
26084
+ });
26085
+ });
26086
+ });
24676
26087
  const glContext = gl;
24677
26088
  if (glContext && image.texture) {
24678
26089
  glContext.deleteTexture(image.texture);
@@ -24695,13 +26106,16 @@ const createSpriteLayer = (options) => {
24695
26106
  }
24696
26107
  });
24697
26108
  images.clear();
26109
+ hitTestTree.clear();
26110
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
26111
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24698
26112
  clearTextureQueue();
24699
26113
  ensureRenderTargetEntries();
24700
26114
  scheduleRender();
24701
26115
  };
24702
26116
  const getAllImageIds = () => Array.from(images.keys());
24703
26117
  const addSpriteInternal = (spriteId, init) => {
24704
- var _a2, _b, _c, _d;
26118
+ var _a2, _b, _c, _d, _e;
24705
26119
  if (sprites.get(spriteId)) {
24706
26120
  return false;
24707
26121
  }
@@ -24762,23 +26176,33 @@ const createSpriteLayer = (options) => {
24762
26176
  }
24763
26177
  }
24764
26178
  const currentLocation = cloneSpriteLocation(init.location);
26179
+ const initialAltitude = (_c = currentLocation.z) != null ? _c : 0;
26180
+ const initialMercator = maplibreGlExports.MercatorCoordinate.fromLngLat(
26181
+ { lng: currentLocation.lng, lat: currentLocation.lat },
26182
+ initialAltitude
26183
+ );
24765
26184
  const spriteState = {
24766
26185
  spriteId,
24767
26186
  // Sprites default to enabled unless explicitly disabled in the init payload.
24768
- isEnabled: (_c = init.isEnabled) != null ? _c : true,
26187
+ isEnabled: (_d = init.isEnabled) != null ? _d : true,
24769
26188
  currentLocation,
24770
26189
  fromLocation: void 0,
24771
26190
  toLocation: void 0,
24772
26191
  images: images2,
24773
26192
  // Tags default to null to simplify downstream comparisons.
24774
- tag: (_d = init.tag) != null ? _d : null,
26193
+ tag: (_e = init.tag) != null ? _e : null,
24775
26194
  interpolationState: null,
24776
26195
  pendingInterpolationOptions: null,
24777
26196
  lastCommandLocation: cloneSpriteLocation(currentLocation),
24778
26197
  lastAutoRotationLocation: cloneSpriteLocation(currentLocation),
24779
- lastAutoRotationAngleDeg: 0
26198
+ lastAutoRotationAngleDeg: 0,
26199
+ cachedMercator: initialMercator,
26200
+ cachedMercatorLng: currentLocation.lng,
26201
+ cachedMercatorLat: currentLocation.lat,
26202
+ cachedMercatorZ: initialAltitude
24780
26203
  };
24781
26204
  sprites.set(spriteId, spriteState);
26205
+ refreshSpriteHitTestBounds(spriteState);
24782
26206
  return true;
24783
26207
  };
24784
26208
  const resolveSpriteInitCollection = (collection) => {
@@ -24813,7 +26237,19 @@ const createSpriteLayer = (options) => {
24813
26237
  }
24814
26238
  return addedCount;
24815
26239
  };
24816
- const removeSpriteInternal = (spriteId) => sprites.delete(spriteId);
26240
+ const removeSpriteInternal = (spriteId) => {
26241
+ const sprite = sprites.get(spriteId);
26242
+ if (!sprite) {
26243
+ return false;
26244
+ }
26245
+ sprite.images.forEach((orderMap) => {
26246
+ orderMap.forEach((image) => {
26247
+ removeImageBoundsFromHitTestTree(image);
26248
+ });
26249
+ });
26250
+ sprites.delete(spriteId);
26251
+ return true;
26252
+ };
24817
26253
  const removeSprite = (spriteId) => {
24818
26254
  const removed = removeSpriteInternal(spriteId);
24819
26255
  if (!removed) {
@@ -24841,6 +26277,9 @@ const createSpriteLayer = (options) => {
24841
26277
  if (removedCount === 0) {
24842
26278
  return 0;
24843
26279
  }
26280
+ hitTestTree.clear();
26281
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
26282
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24844
26283
  sprites.clear();
24845
26284
  ensureRenderTargetEntries();
24846
26285
  scheduleRender();
@@ -24857,6 +26296,9 @@ const createSpriteLayer = (options) => {
24857
26296
  let removedCount = 0;
24858
26297
  sprite.images.forEach((orderMap) => {
24859
26298
  removedCount += orderMap.size;
26299
+ orderMap.forEach((image) => {
26300
+ removeImageBoundsFromHitTestTree(image);
26301
+ });
24860
26302
  });
24861
26303
  sprite.images.clear();
24862
26304
  ensureRenderTargetEntries();
@@ -24907,6 +26349,7 @@ const createSpriteLayer = (options) => {
24907
26349
  }
24908
26350
  syncImageRotationChannel(state);
24909
26351
  setImageState(sprite, state);
26352
+ registerImageBoundsInHitTestTree(sprite, state);
24910
26353
  resultOut.isUpdated = true;
24911
26354
  return true;
24912
26355
  };
@@ -25019,6 +26462,7 @@ const createSpriteLayer = (options) => {
25019
26462
  hasRotationOverride ? rotationOverride != null ? rotationOverride : null : void 0
25020
26463
  );
25021
26464
  }
26465
+ registerImageBoundsInHitTestTree(sprite, state);
25022
26466
  resultOut.isUpdated = true;
25023
26467
  return true;
25024
26468
  };
@@ -25037,6 +26481,10 @@ const createSpriteLayer = (options) => {
25037
26481
  return true;
25038
26482
  };
25039
26483
  const removeSpriteImageInternal = (sprite, subLayer, order, resultOut) => {
26484
+ const state = getImageState(sprite, subLayer, order);
26485
+ if (state) {
26486
+ removeImageBoundsFromHitTestTree(state);
26487
+ }
25040
26488
  const deleted = deleteImageState(sprite, subLayer, order);
25041
26489
  if (deleted) {
25042
26490
  resultOut.isUpdated = true;
@@ -25066,11 +26514,13 @@ const createSpriteLayer = (options) => {
25066
26514
  }
25067
26515
  let updated = false;
25068
26516
  let isRequiredRender = false;
26517
+ let needsHitTestRefresh = false;
25069
26518
  if (update.isEnabled !== void 0) {
25070
26519
  if (sprite.isEnabled !== update.isEnabled) {
25071
26520
  sprite.isEnabled = update.isEnabled;
25072
26521
  updated = true;
25073
26522
  isRequiredRender = true;
26523
+ needsHitTestRefresh = true;
25074
26524
  }
25075
26525
  }
25076
26526
  let interpolationOptionsForLocation = void 0;
@@ -25147,6 +26597,7 @@ const createSpriteLayer = (options) => {
25147
26597
  }
25148
26598
  sprite.pendingInterpolationOptions = null;
25149
26599
  applyAutoRotation(sprite, newCommandLocation);
26600
+ needsHitTestRefresh = true;
25150
26601
  }
25151
26602
  if (update.tag !== void 0) {
25152
26603
  const nextTag = (_a2 = update.tag) != null ? _a2 : null;
@@ -25155,6 +26606,9 @@ const createSpriteLayer = (options) => {
25155
26606
  updated = true;
25156
26607
  }
25157
26608
  }
26609
+ if (needsHitTestRefresh) {
26610
+ refreshSpriteHitTestBounds(sprite);
26611
+ }
25158
26612
  if (isRequiredRender) {
25159
26613
  return "isRequiredRender";
25160
26614
  }
@@ -25389,6 +26843,21 @@ const createSpriteLayer = (options) => {
25389
26843
  updateSprite,
25390
26844
  mutateSprites,
25391
26845
  updateForEach,
26846
+ setHitTestEnabled: (enabled) => {
26847
+ if (isHitTestEnabled === enabled) {
26848
+ return;
26849
+ }
26850
+ isHitTestEnabled = enabled;
26851
+ hitTestTree.clear();
26852
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
26853
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
26854
+ if (!enabled) {
26855
+ return;
26856
+ }
26857
+ sprites.forEach((sprite) => {
26858
+ refreshSpriteHitTestBounds(sprite);
26859
+ });
26860
+ },
25392
26861
  on: addEventListener2,
25393
26862
  off: removeEventListener2
25394
26863
  };