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