maplibre-gl-layers 0.6.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
@@ -1,11 +1,14 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1
4
  /*!
2
5
  * name: maplibre-gl-layers
3
- * version: 0.6.0
6
+ * version: 0.11.0
4
7
  * description: MapLibre's layer extension library enabling the display, movement, and modification of large numbers of dynamic sprite images
5
8
  * author: Kouji Matsui (@kekyo@mi.kekyo.net)
6
9
  * license: MIT
7
10
  * repository.url: https://github.com/kekyo/maplibre-gl-layers.git
8
- * git.commit.hash: 481f544de02fd3e71a2ba6c28bbb7eeb98b49eff
11
+ * git.commit.hash: 371efb126f281333d59ec75cfe788d45f3b1482e
9
12
  */
10
13
  const UNLIMITED_SPRITE_SCALING_OPTIONS = {
11
14
  metersPerPixel: 1,
@@ -37,6 +40,270 @@ const BETTER_TEXTURE_FILTERING_OPTIONS = {
37
40
  generateMipmaps: true,
38
41
  maxAnisotropy: 8
39
42
  };
43
+ class SvgSizeResolutionError extends Error {
44
+ constructor(message, code) {
45
+ super(message);
46
+ __publicField(this, "code");
47
+ this.name = "SvgSizeResolutionError";
48
+ this.code = code;
49
+ }
50
+ }
51
+ const parseNumericLength = (value) => {
52
+ if (!value) {
53
+ return void 0;
54
+ }
55
+ const trimmed = value.trim();
56
+ if (trimmed.length === 0) {
57
+ return void 0;
58
+ }
59
+ const parsed = Number.parseFloat(trimmed);
60
+ if (!Number.isFinite(parsed) || parsed <= 0) {
61
+ return void 0;
62
+ }
63
+ return parsed;
64
+ };
65
+ const extractStyleLength = (styleValue, property) => {
66
+ if (!styleValue) {
67
+ return void 0;
68
+ }
69
+ const declarations = styleValue.split(";").map((decl) => decl.trim()).filter((decl) => decl.length > 0);
70
+ for (const declaration of declarations) {
71
+ const [prop, rawValue] = declaration.split(":");
72
+ if (!prop || !rawValue) {
73
+ continue;
74
+ }
75
+ if (prop.trim().toLowerCase() === property) {
76
+ return parseNumericLength(rawValue);
77
+ }
78
+ }
79
+ return void 0;
80
+ };
81
+ const parseSvgSize = (svgText) => {
82
+ try {
83
+ const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
84
+ const svg = doc.documentElement;
85
+ if (!svg || svg.tagName.toLowerCase() !== "svg") {
86
+ return { hasViewBox: false };
87
+ }
88
+ const attrWidth = parseNumericLength(svg.getAttribute("width"));
89
+ const attrHeight = parseNumericLength(svg.getAttribute("height"));
90
+ const styleWidth = extractStyleLength(svg.getAttribute("style"), "width");
91
+ const styleHeight = extractStyleLength(svg.getAttribute("style"), "height");
92
+ const width = attrWidth != null ? attrWidth : styleWidth;
93
+ const height = attrHeight != null ? attrHeight : styleHeight;
94
+ let viewBoxWidth;
95
+ let viewBoxHeight;
96
+ let hasViewBox = false;
97
+ const viewBox = svg.getAttribute("viewBox");
98
+ if (viewBox) {
99
+ const parts = viewBox.split(/[\s,]+/).map((part) => Number.parseFloat(part)).filter((part) => Number.isFinite(part));
100
+ if (parts.length === 4) {
101
+ viewBoxWidth = parts[2];
102
+ viewBoxHeight = parts[3];
103
+ if (viewBoxWidth > 0 && viewBoxHeight > 0) {
104
+ hasViewBox = true;
105
+ } else {
106
+ viewBoxWidth = void 0;
107
+ viewBoxHeight = void 0;
108
+ }
109
+ }
110
+ }
111
+ return {
112
+ width: width !== void 0 && width > 0 ? width : void 0,
113
+ height: height !== void 0 && height > 0 ? height : void 0,
114
+ viewBoxWidth,
115
+ viewBoxHeight,
116
+ hasViewBox
117
+ };
118
+ } catch (e) {
119
+ return { hasViewBox: false };
120
+ }
121
+ };
122
+ const determineSvgRasterDimensions = (parsed, options) => {
123
+ var _a, _b;
124
+ const overrideWidth = options == null ? void 0 : options.width;
125
+ const overrideHeight = options == null ? void 0 : options.height;
126
+ if (overrideWidth !== void 0 && overrideHeight !== void 0 && overrideWidth > 0 && overrideHeight > 0) {
127
+ return {
128
+ width: Math.max(1, Math.round(overrideWidth)),
129
+ height: Math.max(1, Math.round(overrideHeight))
130
+ };
131
+ }
132
+ const intrinsicWidth = parsed == null ? void 0 : parsed.width;
133
+ const intrinsicHeight = parsed == null ? void 0 : parsed.height;
134
+ const hasValidViewBox = Boolean(
135
+ (parsed == null ? void 0 : parsed.hasViewBox) && parsed.viewBoxWidth !== void 0 && parsed.viewBoxHeight !== void 0 && parsed.viewBoxWidth > 0 && parsed.viewBoxHeight > 0
136
+ );
137
+ const viewBoxAspect = hasValidViewBox ? parsed.viewBoxWidth / parsed.viewBoxHeight : void 0;
138
+ let baseWidth;
139
+ let baseHeight;
140
+ let aspect = intrinsicWidth !== void 0 && intrinsicHeight !== void 0 && intrinsicHeight > 0 ? intrinsicWidth / intrinsicHeight : viewBoxAspect;
141
+ if (intrinsicWidth !== void 0 && intrinsicWidth > 0 && intrinsicHeight !== void 0 && intrinsicHeight > 0) {
142
+ baseWidth = intrinsicWidth;
143
+ baseHeight = intrinsicHeight;
144
+ } else if (intrinsicWidth !== void 0 && intrinsicWidth > 0 && aspect !== void 0) {
145
+ baseWidth = intrinsicWidth;
146
+ baseHeight = intrinsicWidth / aspect;
147
+ } else if (intrinsicHeight !== void 0 && intrinsicHeight > 0 && aspect !== void 0) {
148
+ baseHeight = intrinsicHeight;
149
+ baseWidth = intrinsicHeight * aspect;
150
+ } else if (hasValidViewBox && ((_a = options == null ? void 0 : options.svg) == null ? void 0 : _a.useViewBoxDimensions)) {
151
+ baseWidth = parsed.viewBoxWidth;
152
+ baseHeight = parsed.viewBoxHeight;
153
+ aspect = baseWidth / baseHeight;
154
+ }
155
+ if ((baseWidth === void 0 || baseHeight === void 0) && hasValidViewBox && !((_b = options == null ? void 0 : options.svg) == null ? void 0 : _b.useViewBoxDimensions)) {
156
+ throw new SvgSizeResolutionError(
157
+ "SVG width/height attributes are missing and useViewBoxDimensions option is disabled.",
158
+ "viewbox-disabled"
159
+ );
160
+ }
161
+ if (baseWidth === void 0 || baseHeight === void 0) {
162
+ throw new SvgSizeResolutionError(
163
+ "SVG image lacks sufficient sizing information.",
164
+ "size-missing"
165
+ );
166
+ }
167
+ aspect = aspect != null ? aspect : baseWidth / baseHeight;
168
+ let finalWidth = baseWidth;
169
+ let finalHeight = baseHeight;
170
+ if (overrideWidth !== void 0 && overrideWidth > 0) {
171
+ finalWidth = overrideWidth;
172
+ if (overrideHeight === void 0) {
173
+ if (aspect === void 0) {
174
+ throw new SvgSizeResolutionError(
175
+ "Unable to infer SVG height from width; aspect ratio is undefined.",
176
+ "invalid-dimensions"
177
+ );
178
+ }
179
+ finalHeight = finalWidth / aspect;
180
+ }
181
+ }
182
+ if (overrideHeight !== void 0 && overrideHeight > 0) {
183
+ finalHeight = overrideHeight;
184
+ if (overrideWidth === void 0) {
185
+ if (aspect === void 0) {
186
+ throw new SvgSizeResolutionError(
187
+ "Unable to infer SVG width from height; aspect ratio is undefined.",
188
+ "invalid-dimensions"
189
+ );
190
+ }
191
+ finalWidth = finalHeight * aspect;
192
+ }
193
+ }
194
+ if (!Number.isFinite(finalWidth) || !Number.isFinite(finalHeight) || finalWidth <= 0 || finalHeight <= 0) {
195
+ throw new SvgSizeResolutionError(
196
+ "Resolved SVG dimensions are invalid.",
197
+ "invalid-dimensions"
198
+ );
199
+ }
200
+ return {
201
+ width: Math.max(1, Math.round(finalWidth)),
202
+ height: Math.max(1, Math.round(finalHeight))
203
+ };
204
+ };
205
+ const isSvgMimeType = (mimeType) => {
206
+ if (!mimeType) {
207
+ return false;
208
+ }
209
+ return mimeType.toLowerCase().includes("image/svg");
210
+ };
211
+ const rasterizeSvgWithCanvas = async (blob, width, height, options) => {
212
+ if (typeof document === "undefined") {
213
+ throw new Error(
214
+ "SVG rasterization fallback requires a browser environment"
215
+ );
216
+ }
217
+ const blobUrl = URL.createObjectURL(blob);
218
+ try {
219
+ const image = await new Promise((resolve, reject) => {
220
+ const element = new Image();
221
+ element.onload = () => resolve(element);
222
+ element.onerror = () => reject(new Error("Failed to load SVG for rasterization"));
223
+ element.src = blobUrl;
224
+ });
225
+ const canvas = document.createElement("canvas");
226
+ canvas.width = width;
227
+ canvas.height = height;
228
+ const ctx = canvas.getContext("2d");
229
+ if (!ctx) {
230
+ throw new Error("Failed to acquire 2D context for SVG rasterization");
231
+ }
232
+ ctx.clearRect(0, 0, width, height);
233
+ ctx.imageSmoothingEnabled = true;
234
+ if ((options == null ? void 0 : options.resizeQuality) === "pixelated") {
235
+ ctx.imageSmoothingEnabled = false;
236
+ } else if (options == null ? void 0 : options.resizeQuality) {
237
+ ctx.imageSmoothingQuality = options.resizeQuality;
238
+ }
239
+ ctx.drawImage(image, 0, 0, width, height);
240
+ try {
241
+ return await createImageBitmap(canvas);
242
+ } catch (e) {
243
+ const canvasBlob = await new Promise((resolve, reject) => {
244
+ canvas.toBlob((result) => {
245
+ if (result) {
246
+ resolve(result);
247
+ } else {
248
+ reject(
249
+ new Error("Failed to convert canvas to blob during rasterization")
250
+ );
251
+ }
252
+ });
253
+ });
254
+ return await createImageBitmap(canvasBlob);
255
+ }
256
+ } finally {
257
+ URL.revokeObjectURL(blobUrl);
258
+ }
259
+ };
260
+ const resolveSvgBitmapWithFallback = async (blob, width, height, options) => {
261
+ const bitmapOptions = {
262
+ resizeWidth: width,
263
+ resizeHeight: height
264
+ };
265
+ if (options == null ? void 0 : options.resizeQuality) {
266
+ bitmapOptions.resizeQuality = options.resizeQuality;
267
+ }
268
+ try {
269
+ return await createImageBitmap(blob, bitmapOptions);
270
+ } catch (error) {
271
+ return await rasterizeSvgWithCanvas(blob, width, height, options);
272
+ }
273
+ };
274
+ const internalReadImageBitmap = async (blob, shouldTreatAsSvg, options) => {
275
+ const svgOptions = options == null ? void 0 : options.svg;
276
+ if (shouldTreatAsSvg) {
277
+ let parsed = null;
278
+ if ((svgOptions == null ? void 0 : svgOptions.inspectSize) !== false) {
279
+ const text = await blob.text();
280
+ parsed = parseSvgSize(text);
281
+ }
282
+ const { width, height } = determineSvgRasterDimensions(parsed, options);
283
+ return await resolveSvgBitmapWithFallback(blob, width, height, options);
284
+ }
285
+ return await createImageBitmap(blob, {
286
+ resizeWidth: options == null ? void 0 : options.width,
287
+ resizeHeight: options == null ? void 0 : options.height,
288
+ resizeQuality: options == null ? void 0 : options.resizeQuality
289
+ });
290
+ };
291
+ const readImageBitmap = (blob, options) => {
292
+ const svgOptions = options == null ? void 0 : options.svg;
293
+ const shouldTreatAsSvg = (svgOptions == null ? void 0 : svgOptions.assumeSvg) === true;
294
+ return internalReadImageBitmap(blob, shouldTreatAsSvg, options);
295
+ };
296
+ const loadImageBitmap = async (url, options) => {
297
+ var _a;
298
+ const response = await fetch(url);
299
+ if (!response.ok) {
300
+ throw new Error(`Failed to fetch image from ${url}`);
301
+ }
302
+ const mimeType = response.headers.get("content-type");
303
+ const blob = await response.blob();
304
+ const shouldTreatAsSvg = ((_a = options == null ? void 0 : options.svg) == null ? void 0 : _a.assumeSvg) === true || isSvgMimeType(mimeType);
305
+ return await internalReadImageBitmap(blob, shouldTreatAsSvg, options);
306
+ };
40
307
  var maplibreGl$1 = { exports: {} };
41
308
  /**
42
309
  * MapLibre GL JS
@@ -21111,14 +21378,6 @@ function transformMat4(out, a, m) {
21111
21378
  return a;
21112
21379
  };
21113
21380
  })();
21114
- const loadImageBitmap = async (url) => {
21115
- const response = await fetch(url);
21116
- if (!response.ok) {
21117
- throw new Error(`Failed to fetch image from ${url}`);
21118
- }
21119
- const blob = await response.blob();
21120
- return await createImageBitmap(blob);
21121
- };
21122
21381
  const cloneSpriteLocation = (location2) => {
21123
21382
  if (location2.z === void 0) {
21124
21383
  return { lng: location2.lng, lat: location2.lat };
@@ -21175,7 +21434,7 @@ const computeFeedforwardTarget = (previous, next) => {
21175
21434
  }
21176
21435
  return target;
21177
21436
  };
21178
- const normaliseOptions$1 = (options) => {
21437
+ const normalizeOptions$2 = (options) => {
21179
21438
  var _a;
21180
21439
  return {
21181
21440
  durationMs: Math.max(0, options.durationMs),
@@ -21185,7 +21444,7 @@ const normaliseOptions$1 = (options) => {
21185
21444
  };
21186
21445
  const createInterpolationState = (params) => {
21187
21446
  const { currentLocation, lastCommandLocation, nextCommandLocation } = params;
21188
- const options = normaliseOptions$1(params.options);
21447
+ const options = normalizeOptions$2(params.options);
21189
21448
  const from = cloneSpriteLocation(currentLocation);
21190
21449
  const easing = resolveEasing(options.easing);
21191
21450
  let to;
@@ -21230,8 +21489,8 @@ const evaluateInterpolation = (params) => {
21230
21489
  };
21231
21490
  };
21232
21491
  const NUMERIC_EPSILON = 1e-6;
21233
- const normaliseDuration = (durationMs) => Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0;
21234
- const normaliseDelta = (delta) => {
21492
+ const normalizeDuration$1 = (durationMs) => Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0;
21493
+ const normalizeDelta = (delta) => {
21235
21494
  if (!Number.isFinite(delta)) {
21236
21495
  return 0;
21237
21496
  }
@@ -21243,16 +21502,24 @@ const normaliseDelta = (delta) => {
21243
21502
  }
21244
21503
  return adjusted;
21245
21504
  };
21246
- const normaliseOptions = (options) => {
21505
+ const normalizeOptions$1 = (options) => {
21506
+ var _a;
21247
21507
  return {
21248
- durationMs: normaliseDuration(options.durationMs),
21249
- easing: resolveEasing(options.easing)
21508
+ durationMs: normalizeDuration$1(options.durationMs),
21509
+ easing: resolveEasing(options.easing),
21510
+ mode: (_a = options.mode) != null ? _a : "feedback"
21250
21511
  };
21251
21512
  };
21252
- const createNumericInterpolationState = (params) => {
21513
+ const createDegreeInterpolationState = (params) => {
21253
21514
  const { currentValue, targetValue } = params;
21254
- const options = normaliseOptions(params.options);
21255
- const delta = normaliseDelta(targetValue - currentValue);
21515
+ const options = normalizeOptions$1(params.options);
21516
+ let effectiveTarget = targetValue;
21517
+ const previousCommand = params.previousCommandValue;
21518
+ if (options.mode === "feedforward" && previousCommand !== void 0 && Number.isFinite(previousCommand)) {
21519
+ const commandDelta = normalizeDelta(targetValue - previousCommand);
21520
+ effectiveTarget = targetValue + commandDelta;
21521
+ }
21522
+ const delta = normalizeDelta(effectiveTarget - currentValue);
21256
21523
  const pathTarget = currentValue + delta;
21257
21524
  const requiresInterpolation = options.durationMs > 0 && Math.abs(delta) > NUMERIC_EPSILON;
21258
21525
  const state = {
@@ -21260,7 +21527,7 @@ const createNumericInterpolationState = (params) => {
21260
21527
  easing: options.easing,
21261
21528
  from: currentValue,
21262
21529
  to: pathTarget,
21263
- finalValue: targetValue,
21530
+ finalValue: effectiveTarget,
21264
21531
  startTimestamp: -1
21265
21532
  };
21266
21533
  return {
@@ -21268,7 +21535,7 @@ const createNumericInterpolationState = (params) => {
21268
21535
  requiresInterpolation
21269
21536
  };
21270
21537
  };
21271
- const clamp01 = (value) => {
21538
+ const clamp01$1 = (value) => {
21272
21539
  if (!Number.isFinite(value)) {
21273
21540
  return 1;
21274
21541
  }
@@ -21280,7 +21547,7 @@ const clamp01 = (value) => {
21280
21547
  }
21281
21548
  return value;
21282
21549
  };
21283
- const evaluateNumericInterpolation = (params) => {
21550
+ const evaluateDegreeInterpolation = (params) => {
21284
21551
  const { state } = params;
21285
21552
  const timestamp = Number.isFinite(params.timestamp) ? params.timestamp : Date.now();
21286
21553
  const duration = Math.max(0, state.durationMs);
@@ -21294,7 +21561,7 @@ const evaluateNumericInterpolation = (params) => {
21294
21561
  }
21295
21562
  const elapsed = timestamp - effectiveStart;
21296
21563
  const rawProgress = duration <= 0 ? 1 : elapsed / duration;
21297
- const eased = clamp01(state.easing(rawProgress));
21564
+ const eased = clamp01$1(state.easing(rawProgress));
21298
21565
  const interpolated = state.from + (state.to - state.from) * eased;
21299
21566
  const completed = rawProgress >= 1;
21300
21567
  return {
@@ -21303,7 +21570,7 @@ const evaluateNumericInterpolation = (params) => {
21303
21570
  effectiveStartTimestamp: effectiveStart
21304
21571
  };
21305
21572
  };
21306
- const normaliseAngleDeg = (angle) => {
21573
+ const normalizeAngleDeg = (angle) => {
21307
21574
  if (!Number.isFinite(angle)) {
21308
21575
  return 0;
21309
21576
  }
@@ -21313,17 +21580,19 @@ const normaliseAngleDeg = (angle) => {
21313
21580
  };
21314
21581
  const resolveRotationTarget = (params) => {
21315
21582
  const options = params.options;
21316
- const targetAngle = normaliseAngleDeg(params.targetAngleDeg);
21317
- const currentAngle = normaliseAngleDeg(params.currentAngleDeg);
21583
+ const targetAngle = normalizeAngleDeg(params.targetAngleDeg);
21584
+ const currentAngle = normalizeAngleDeg(params.currentAngleDeg);
21585
+ const previousCommandAngleDeg = params.previousCommandAngleDeg !== void 0 ? normalizeAngleDeg(params.previousCommandAngleDeg) : void 0;
21318
21586
  if (!options || options.durationMs <= 0) {
21319
21587
  return {
21320
21588
  nextAngleDeg: targetAngle,
21321
21589
  interpolationState: null
21322
21590
  };
21323
21591
  }
21324
- const { state, requiresInterpolation } = createNumericInterpolationState({
21592
+ const { state, requiresInterpolation } = createDegreeInterpolationState({
21325
21593
  currentValue: currentAngle,
21326
21594
  targetValue: targetAngle,
21595
+ previousCommandValue: previousCommandAngleDeg,
21327
21596
  options
21328
21597
  });
21329
21598
  if (!requiresInterpolation) {
@@ -21665,13 +21934,6 @@ const applySurfaceDisplacement = (baseLng, baseLat, east, north) => {
21665
21934
  lat: baseLat + deltaLat
21666
21935
  };
21667
21936
  };
21668
- const screenToClip = (x, y, drawingBufferWidth, drawingBufferHeight, pixelRatio) => {
21669
- const deviceX = x * pixelRatio;
21670
- const deviceY = y * pixelRatio;
21671
- const clipX = deviceX / drawingBufferWidth * 2 - 1;
21672
- const clipY = 1 - deviceY / drawingBufferHeight * 2;
21673
- return [clipX, clipY];
21674
- };
21675
21937
  const clipToScreen = (clipPosition, drawingBufferWidth, drawingBufferHeight, pixelRatio) => {
21676
21938
  const [clipX, clipY, , clipW] = clipPosition;
21677
21939
  if (!Number.isFinite(clipW) || clipW === 0) {
@@ -21816,41 +22078,55 @@ const calculateBillboardCenterPosition = (params) => {
21816
22078
  offsetShift
21817
22079
  };
21818
22080
  };
21819
- const BILLBOARD_BASE_CORNERS = [
21820
- [-1, 1],
21821
- [1, 1],
21822
- [-1, -1],
21823
- [1, -1]
21824
- ];
21825
- const calculateBillboardCornerScreenPositions = (params) => {
22081
+ const computeSurfaceCornerShaderModel = (params) => {
21826
22082
  var _a, _b;
21827
- 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;
21828
22093
  if (halfWidth <= 0 || halfHeight <= 0) {
21829
- 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
+ }));
21830
22104
  }
21831
- const anchorOffsetX = ((_a = anchor == null ? void 0 : anchor.x) != null ? _a : 0) * halfWidth;
21832
- 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;
21833
22107
  const rad = -totalRotateDeg * DEG2RAD;
21834
- const cosR = Math.cos(rad);
21835
22108
  const sinR = Math.sin(rad);
21836
- const corners = [];
21837
- for (let i = 0; i < BILLBOARD_BASE_CORNERS.length; i++) {
21838
- const [cornerXNorm, cornerYNorm] = BILLBOARD_BASE_CORNERS[i];
21839
- const [u, v] = UV_CORNERS[i];
21840
- const cornerX = cornerXNorm * halfWidth;
21841
- const cornerY = cornerYNorm * halfHeight;
21842
- const shiftedX = cornerX - anchorOffsetX;
21843
- const shiftedY = cornerY - anchorOffsetY;
21844
- const rotatedX = shiftedX * cosR - shiftedY * sinR;
21845
- const rotatedY = shiftedX * sinR + shiftedY * cosR;
21846
- corners.push({
21847
- x: centerX + rotatedX,
21848
- y: centerY - rotatedY,
21849
- u,
21850
- v
21851
- });
21852
- }
21853
- 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
+ });
21854
22130
  };
21855
22131
  const calculateSurfaceCenterPosition = (params) => {
21856
22132
  var _a, _b;
@@ -21960,7 +22236,7 @@ const calculateSurfaceCenterPosition = (params) => {
21960
22236
  anchorlessLngLat
21961
22237
  };
21962
22238
  };
21963
- const SURFACE_BASE_CORNERS = [
22239
+ const SURFACE_BASE_CORNERS$1 = [
21964
22240
  [-1, 1],
21965
22241
  [1, 1],
21966
22242
  [-1, -1],
@@ -21976,7 +22252,7 @@ const calculateSurfaceCornerDisplacements = (params) => {
21976
22252
  offsetMeters
21977
22253
  } = params;
21978
22254
  if (worldWidthMeters <= 0 || worldHeightMeters <= 0) {
21979
- return SURFACE_BASE_CORNERS.map(() => ({
22255
+ return SURFACE_BASE_CORNERS$1.map(() => ({
21980
22256
  east: offsetMeters.east,
21981
22257
  north: offsetMeters.north
21982
22258
  }));
@@ -21989,7 +22265,7 @@ const calculateSurfaceCornerDisplacements = (params) => {
21989
22265
  const cosR = Math.cos(rad);
21990
22266
  const sinR = Math.sin(rad);
21991
22267
  const corners = [];
21992
- for (const [eastNorm, northNorm] of SURFACE_BASE_CORNERS) {
22268
+ for (const [eastNorm, northNorm] of SURFACE_BASE_CORNERS$1) {
21993
22269
  const cornerEast = eastNorm * halfWidth;
21994
22270
  const cornerNorth = northNorm * halfHeight;
21995
22271
  const localEast = cornerEast - anchorEast;
@@ -22003,115 +22279,602 @@ const calculateSurfaceCornerDisplacements = (params) => {
22003
22279
  }
22004
22280
  return corners;
22005
22281
  };
22006
- const DEFAULT_ANCHOR = { x: 0, y: 0 };
22007
- const DEFAULT_AUTO_ROTATION_MIN_DISTANCE_METERS = 20;
22008
- const DEFAULT_IMAGE_OFFSET = {
22009
- offsetMeters: 0,
22010
- offsetDeg: 0
22011
- };
22012
- const MIN_CLIP_W = 1e-6;
22013
- const MIN_CLIP_Z_EPSILON = 1e-7;
22014
- const EPS_NDC = 1e-6;
22015
- const ORDER_MAX = 16;
22016
- const ORDER_BUCKET = 16;
22017
- const MIN_FILTER_VALUES = [
22018
- "nearest",
22019
- "linear",
22020
- "nearest-mipmap-nearest",
22021
- "nearest-mipmap-linear",
22022
- "linear-mipmap-nearest",
22023
- "linear-mipmap-linear"
22024
- ];
22025
- const MAG_FILTER_VALUES = [
22026
- "nearest",
22027
- "linear"
22028
- ];
22029
- const MIPMAP_MIN_FILTERS = /* @__PURE__ */ new Set([
22030
- "nearest-mipmap-nearest",
22031
- "nearest-mipmap-linear",
22032
- "linear-mipmap-nearest",
22033
- "linear-mipmap-linear"
22034
- ]);
22035
- const filterRequiresMipmaps = (filter) => MIPMAP_MIN_FILTERS.has(filter);
22036
- const resolveTextureFilteringOptions = (options) => {
22037
- var _a, _b;
22038
- const minCandidate = options == null ? void 0 : options.minFilter;
22039
- const minFilter = MIN_FILTER_VALUES.includes(
22040
- minCandidate
22041
- ) ? minCandidate : DEFAULT_TEXTURE_FILTERING_OPTIONS.minFilter;
22042
- const magCandidate = options == null ? void 0 : options.magFilter;
22043
- const magFilter = MAG_FILTER_VALUES.includes(
22044
- magCandidate
22045
- ) ? magCandidate : DEFAULT_TEXTURE_FILTERING_OPTIONS.magFilter;
22046
- let generateMipmaps = (_a = options == null ? void 0 : options.generateMipmaps) != null ? _a : DEFAULT_TEXTURE_FILTERING_OPTIONS.generateMipmaps;
22047
- if (filterRequiresMipmaps(minFilter)) {
22048
- generateMipmaps = true;
22049
- }
22050
- let maxAnisotropy = (_b = options == null ? void 0 : options.maxAnisotropy) != null ? _b : DEFAULT_TEXTURE_FILTERING_OPTIONS.maxAnisotropy;
22051
- if (!Number.isFinite(maxAnisotropy) || maxAnisotropy < 1) {
22052
- maxAnisotropy = 1;
22053
- }
22282
+ const DISTANCE_EPSILON = 1e-6;
22283
+ const normalizeDuration = (durationMs) => Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0;
22284
+ const normalizeOptions = (options) => {
22285
+ var _a;
22054
22286
  return {
22055
- minFilter,
22056
- magFilter,
22057
- generateMipmaps,
22058
- maxAnisotropy
22287
+ durationMs: normalizeDuration(options.durationMs),
22288
+ easing: resolveEasing(options.easing),
22289
+ mode: (_a = options.mode) != null ? _a : "feedback"
22059
22290
  };
22060
22291
  };
22061
- const ANISOTROPY_EXTENSION_NAMES = [
22062
- "EXT_texture_filter_anisotropic",
22063
- "WEBKIT_EXT_texture_filter_anisotropic",
22064
- "MOZ_EXT_texture_filter_anisotropic"
22065
- ];
22066
- const resolveAnisotropyExtension = (glContext) => {
22067
- for (const name of ANISOTROPY_EXTENSION_NAMES) {
22068
- const extension = glContext.getExtension(name);
22069
- if (extension) {
22070
- return extension;
22071
- }
22292
+ const createDistanceInterpolationState = (params) => {
22293
+ const { currentValue, targetValue } = params;
22294
+ const options = normalizeOptions(params.options);
22295
+ let effectiveTarget = targetValue;
22296
+ const previousCommand = params.previousCommandValue;
22297
+ if (options.mode === "feedforward" && previousCommand !== void 0 && Number.isFinite(previousCommand)) {
22298
+ const commandDelta = targetValue - previousCommand;
22299
+ effectiveTarget = targetValue + commandDelta;
22072
22300
  }
22073
- return null;
22301
+ const delta = effectiveTarget - currentValue;
22302
+ const requiresInterpolation = options.durationMs > 0 && Math.abs(delta) > DISTANCE_EPSILON;
22303
+ const state = {
22304
+ durationMs: options.durationMs,
22305
+ easing: options.easing,
22306
+ from: currentValue,
22307
+ to: currentValue + delta,
22308
+ finalValue: effectiveTarget,
22309
+ startTimestamp: -1
22310
+ };
22311
+ return {
22312
+ state,
22313
+ requiresInterpolation
22314
+ };
22074
22315
  };
22075
- const isPowerOfTwo = (value) => value > 0 && (value & value - 1) === 0;
22076
- const resolveGlMinFilter = (glContext, filter) => {
22077
- switch (filter) {
22078
- case "nearest":
22079
- return glContext.NEAREST;
22080
- case "nearest-mipmap-nearest":
22081
- return glContext.NEAREST_MIPMAP_NEAREST;
22082
- case "nearest-mipmap-linear":
22083
- return glContext.NEAREST_MIPMAP_LINEAR;
22084
- case "linear-mipmap-nearest":
22085
- return glContext.LINEAR_MIPMAP_NEAREST;
22086
- case "linear-mipmap-linear":
22087
- return glContext.LINEAR_MIPMAP_LINEAR;
22088
- case "linear":
22089
- default:
22090
- return glContext.LINEAR;
22316
+ const clamp01 = (value) => {
22317
+ if (!Number.isFinite(value)) {
22318
+ return 1;
22091
22319
  }
22092
- };
22093
- const resolveGlMagFilter = (glContext, filter) => {
22094
- switch (filter) {
22095
- case "nearest":
22096
- return glContext.NEAREST;
22097
- case "linear":
22098
- default:
22099
- return glContext.LINEAR;
22320
+ if (value <= 0) {
22321
+ return 0;
22100
22322
  }
22101
- };
22102
- const calculatePerspectiveRatio = (mapInstance, location2) => {
22103
- var _a, _b, _c;
22104
- const transform = mapInstance.transform;
22105
- if (!transform) {
22323
+ if (value >= 1) {
22106
22324
  return 1;
22107
22325
  }
22108
- const mercatorMatrix = (_a = transform.mercatorMatrix) != null ? _a : transform._mercatorMatrix;
22326
+ return value;
22327
+ };
22328
+ const evaluateDistanceInterpolation = (params) => {
22329
+ const { state } = params;
22330
+ const timestamp = Number.isFinite(params.timestamp) ? params.timestamp : Date.now();
22331
+ const duration = Math.max(0, state.durationMs);
22332
+ const effectiveStart = state.startTimestamp >= 0 ? state.startTimestamp : timestamp;
22333
+ if (duration === 0 || Math.abs(state.to - state.from) <= DISTANCE_EPSILON) {
22334
+ return {
22335
+ value: state.finalValue,
22336
+ completed: true,
22337
+ effectiveStartTimestamp: effectiveStart
22338
+ };
22339
+ }
22340
+ const elapsed = timestamp - effectiveStart;
22341
+ const rawProgress = duration <= 0 ? 1 : elapsed / duration;
22342
+ const eased = clamp01(state.easing(rawProgress));
22343
+ const interpolated = state.from + (state.to - state.from) * eased;
22344
+ const completed = rawProgress >= 1;
22345
+ return {
22346
+ value: completed ? state.finalValue : interpolated,
22347
+ completed,
22348
+ effectiveStartTimestamp: effectiveStart
22349
+ };
22350
+ };
22351
+ const stepDegreeInterpolationState = (interpolationState, timestamp, applyValue, options) => {
22352
+ var _a, _b;
22353
+ if (!interpolationState) {
22354
+ return { state: null, active: false };
22355
+ }
22356
+ const evaluation = evaluateDegreeInterpolation({
22357
+ state: interpolationState,
22358
+ timestamp
22359
+ });
22360
+ if (interpolationState.startTimestamp < 0) {
22361
+ interpolationState.startTimestamp = evaluation.effectiveStartTimestamp;
22362
+ }
22363
+ const normalizeValue = (_a = options == null ? void 0 : options.normalize) != null ? _a : ((value) => value);
22364
+ const applyFinalValue = (_b = options == null ? void 0 : options.applyFinalValue) != null ? _b : applyValue;
22365
+ const interpolatedValue = normalizeValue(evaluation.value);
22366
+ applyValue(interpolatedValue);
22367
+ if (evaluation.completed) {
22368
+ const finalValue = normalizeValue(interpolationState.finalValue);
22369
+ applyFinalValue(finalValue);
22370
+ return { state: null, active: false };
22371
+ }
22372
+ return { state: interpolationState, active: true };
22373
+ };
22374
+ const updateImageDisplayedRotation = (image, optionsOverride) => {
22375
+ const targetAngle = normalizeAngleDeg(
22376
+ image.resolvedBaseRotateDeg + image.rotateDeg
22377
+ );
22378
+ const currentAngle = Number.isFinite(image.displayedRotateDeg) ? image.displayedRotateDeg : targetAngle;
22379
+ const previousCommandAngle = image.lastCommandRotateDeg;
22380
+ const options = optionsOverride === void 0 ? image.rotationInterpolationOptions : optionsOverride;
22381
+ const { nextAngleDeg, interpolationState } = resolveRotationTarget({
22382
+ currentAngleDeg: currentAngle,
22383
+ targetAngleDeg: targetAngle,
22384
+ previousCommandAngleDeg: previousCommandAngle,
22385
+ options: options != null ? options : void 0
22386
+ });
22387
+ image.displayedRotateDeg = nextAngleDeg;
22388
+ image.rotationInterpolationState = interpolationState;
22389
+ if (!interpolationState) {
22390
+ image.displayedRotateDeg = targetAngle;
22391
+ }
22392
+ image.lastCommandRotateDeg = targetAngle;
22393
+ };
22394
+ const syncImageRotationChannel = (image, optionsOverride) => {
22395
+ updateImageDisplayedRotation(image, optionsOverride);
22396
+ };
22397
+ const stepRotationInterpolation = (image, timestamp) => {
22398
+ const { state, active } = stepDegreeInterpolationState(
22399
+ image.rotationInterpolationState,
22400
+ timestamp,
22401
+ (value) => {
22402
+ image.displayedRotateDeg = value;
22403
+ },
22404
+ {
22405
+ normalize: normalizeAngleDeg
22406
+ }
22407
+ );
22408
+ image.rotationInterpolationState = state;
22409
+ return active;
22410
+ };
22411
+ const stepOffsetDegInterpolation = (image, timestamp) => {
22412
+ const { state, active } = stepDegreeInterpolationState(
22413
+ image.offsetDegInterpolationState,
22414
+ timestamp,
22415
+ (value) => {
22416
+ image.offset.offsetDeg = value;
22417
+ }
22418
+ );
22419
+ image.offsetDegInterpolationState = state;
22420
+ return active;
22421
+ };
22422
+ const clearOffsetDegInterpolation = (image) => {
22423
+ image.offsetDegInterpolationState = null;
22424
+ };
22425
+ const stepDistanceInterpolationState = (interpolationState, timestamp, applyValue) => {
22426
+ if (!interpolationState) {
22427
+ return { state: null, active: false };
22428
+ }
22429
+ const evaluation = evaluateDistanceInterpolation({
22430
+ state: interpolationState,
22431
+ timestamp
22432
+ });
22433
+ if (interpolationState.startTimestamp < 0) {
22434
+ interpolationState.startTimestamp = evaluation.effectiveStartTimestamp;
22435
+ }
22436
+ applyValue(evaluation.value);
22437
+ if (evaluation.completed) {
22438
+ applyValue(interpolationState.finalValue);
22439
+ return { state: null, active: false };
22440
+ }
22441
+ return { state: interpolationState, active: true };
22442
+ };
22443
+ const clearOffsetMetersInterpolation = (image) => {
22444
+ image.offsetMetersInterpolationState = null;
22445
+ };
22446
+ const applyOffsetDegUpdate = (image, nextOffset, interpolationOptions) => {
22447
+ const options = interpolationOptions;
22448
+ if (!options || options.durationMs <= 0) {
22449
+ image.offset.offsetDeg = nextOffset.offsetDeg;
22450
+ image.offsetDegInterpolationState = null;
22451
+ image.lastCommandOffsetDeg = nextOffset.offsetDeg;
22452
+ return;
22453
+ }
22454
+ const { state, requiresInterpolation } = createDegreeInterpolationState({
22455
+ currentValue: image.offset.offsetDeg,
22456
+ targetValue: nextOffset.offsetDeg,
22457
+ previousCommandValue: image.lastCommandOffsetDeg,
22458
+ options
22459
+ });
22460
+ image.lastCommandOffsetDeg = nextOffset.offsetDeg;
22461
+ if (requiresInterpolation) {
22462
+ image.offsetDegInterpolationState = state;
22463
+ } else {
22464
+ image.offset.offsetDeg = nextOffset.offsetDeg;
22465
+ image.offsetDegInterpolationState = null;
22466
+ }
22467
+ };
22468
+ const applyOffsetMetersUpdate = (image, nextOffset, interpolationOptions) => {
22469
+ const options = interpolationOptions;
22470
+ if (!options || options.durationMs <= 0) {
22471
+ image.offset.offsetMeters = nextOffset.offsetMeters;
22472
+ image.offsetMetersInterpolationState = null;
22473
+ image.lastCommandOffsetMeters = nextOffset.offsetMeters;
22474
+ return;
22475
+ }
22476
+ const { state, requiresInterpolation } = createDistanceInterpolationState({
22477
+ currentValue: image.offset.offsetMeters,
22478
+ targetValue: nextOffset.offsetMeters,
22479
+ previousCommandValue: image.lastCommandOffsetMeters,
22480
+ options
22481
+ });
22482
+ image.lastCommandOffsetMeters = nextOffset.offsetMeters;
22483
+ if (requiresInterpolation) {
22484
+ image.offsetMetersInterpolationState = state;
22485
+ } else {
22486
+ image.offset.offsetMeters = nextOffset.offsetMeters;
22487
+ image.offsetMetersInterpolationState = null;
22488
+ }
22489
+ };
22490
+ const stepOffsetMetersInterpolation = (image, timestamp) => {
22491
+ const { state, active } = stepDistanceInterpolationState(
22492
+ image.offsetMetersInterpolationState,
22493
+ timestamp,
22494
+ (value) => {
22495
+ image.offset.offsetMeters = value;
22496
+ }
22497
+ );
22498
+ image.offsetMetersInterpolationState = state;
22499
+ return active;
22500
+ };
22501
+ const IMAGE_INTERPOLATION_STEPPERS = [
22502
+ stepRotationInterpolation,
22503
+ stepOffsetDegInterpolation,
22504
+ stepOffsetMetersInterpolation
22505
+ ];
22506
+ const stepSpriteImageInterpolations = (image, timestamp) => {
22507
+ let active = false;
22508
+ for (const stepper of IMAGE_INTERPOLATION_STEPPERS) {
22509
+ if (stepper(image, timestamp)) {
22510
+ active = true;
22511
+ }
22512
+ }
22513
+ return active;
22514
+ };
22515
+ const applyOffsetUpdate = (image, nextOffset, options = {}) => {
22516
+ applyOffsetDegUpdate(image, nextOffset, options.deg);
22517
+ applyOffsetMetersUpdate(image, nextOffset, options.meters);
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
+ };
22768
+ const DEFAULT_ANCHOR = { x: 0, y: 0 };
22769
+ const DEFAULT_AUTO_ROTATION_MIN_DISTANCE_METERS = 20;
22770
+ const DEFAULT_IMAGE_OFFSET = {
22771
+ offsetMeters: 0,
22772
+ offsetDeg: 0
22773
+ };
22774
+ const HIT_TEST_QUERY_RADIUS_PIXELS = 32;
22775
+ const MIN_CLIP_W = 1e-6;
22776
+ const MIN_CLIP_Z_EPSILON = 1e-7;
22777
+ const EPS_NDC = 1e-6;
22778
+ const ORDER_MAX = 16;
22779
+ const ORDER_BUCKET = 16;
22780
+ const MIN_FILTER_VALUES = [
22781
+ "nearest",
22782
+ "linear",
22783
+ "nearest-mipmap-nearest",
22784
+ "nearest-mipmap-linear",
22785
+ "linear-mipmap-nearest",
22786
+ "linear-mipmap-linear"
22787
+ ];
22788
+ const MAG_FILTER_VALUES = [
22789
+ "nearest",
22790
+ "linear"
22791
+ ];
22792
+ const MIPMAP_MIN_FILTERS = /* @__PURE__ */ new Set([
22793
+ "nearest-mipmap-nearest",
22794
+ "nearest-mipmap-linear",
22795
+ "linear-mipmap-nearest",
22796
+ "linear-mipmap-linear"
22797
+ ]);
22798
+ const filterRequiresMipmaps = (filter) => MIPMAP_MIN_FILTERS.has(filter);
22799
+ const resolveTextureFilteringOptions = (options) => {
22800
+ var _a, _b;
22801
+ const minCandidate = options == null ? void 0 : options.minFilter;
22802
+ const minFilter = MIN_FILTER_VALUES.includes(
22803
+ minCandidate
22804
+ ) ? minCandidate : DEFAULT_TEXTURE_FILTERING_OPTIONS.minFilter;
22805
+ const magCandidate = options == null ? void 0 : options.magFilter;
22806
+ const magFilter = MAG_FILTER_VALUES.includes(
22807
+ magCandidate
22808
+ ) ? magCandidate : DEFAULT_TEXTURE_FILTERING_OPTIONS.magFilter;
22809
+ let generateMipmaps = (_a = options == null ? void 0 : options.generateMipmaps) != null ? _a : DEFAULT_TEXTURE_FILTERING_OPTIONS.generateMipmaps;
22810
+ if (filterRequiresMipmaps(minFilter)) {
22811
+ generateMipmaps = true;
22812
+ }
22813
+ let maxAnisotropy = (_b = options == null ? void 0 : options.maxAnisotropy) != null ? _b : DEFAULT_TEXTURE_FILTERING_OPTIONS.maxAnisotropy;
22814
+ if (!Number.isFinite(maxAnisotropy) || maxAnisotropy < 1) {
22815
+ maxAnisotropy = 1;
22816
+ }
22817
+ return {
22818
+ minFilter,
22819
+ magFilter,
22820
+ generateMipmaps,
22821
+ maxAnisotropy
22822
+ };
22823
+ };
22824
+ const ANISOTROPY_EXTENSION_NAMES = [
22825
+ "EXT_texture_filter_anisotropic",
22826
+ "WEBKIT_EXT_texture_filter_anisotropic",
22827
+ "MOZ_EXT_texture_filter_anisotropic"
22828
+ ];
22829
+ const resolveAnisotropyExtension = (glContext) => {
22830
+ for (const name of ANISOTROPY_EXTENSION_NAMES) {
22831
+ const extension = glContext.getExtension(name);
22832
+ if (extension) {
22833
+ return extension;
22834
+ }
22835
+ }
22836
+ return null;
22837
+ };
22838
+ const isPowerOfTwo = (value) => value > 0 && (value & value - 1) === 0;
22839
+ const resolveGlMinFilter = (glContext, filter) => {
22840
+ switch (filter) {
22841
+ case "nearest":
22842
+ return glContext.NEAREST;
22843
+ case "nearest-mipmap-nearest":
22844
+ return glContext.NEAREST_MIPMAP_NEAREST;
22845
+ case "nearest-mipmap-linear":
22846
+ return glContext.NEAREST_MIPMAP_LINEAR;
22847
+ case "linear-mipmap-nearest":
22848
+ return glContext.LINEAR_MIPMAP_NEAREST;
22849
+ case "linear-mipmap-linear":
22850
+ return glContext.LINEAR_MIPMAP_LINEAR;
22851
+ case "linear":
22852
+ default:
22853
+ return glContext.LINEAR;
22854
+ }
22855
+ };
22856
+ const resolveGlMagFilter = (glContext, filter) => {
22857
+ switch (filter) {
22858
+ case "nearest":
22859
+ return glContext.NEAREST;
22860
+ case "linear":
22861
+ default:
22862
+ return glContext.LINEAR;
22863
+ }
22864
+ };
22865
+ const calculatePerspectiveRatio = (mapInstance, location2, cachedMercator) => {
22866
+ var _a, _b, _c;
22867
+ const transform = mapInstance.transform;
22868
+ if (!transform) {
22869
+ return 1;
22870
+ }
22871
+ const mercatorMatrix = (_a = transform.mercatorMatrix) != null ? _a : transform._mercatorMatrix;
22109
22872
  const cameraToCenterDistance = transform.cameraToCenterDistance;
22110
22873
  if (!mercatorMatrix || typeof cameraToCenterDistance !== "number" || !Number.isFinite(cameraToCenterDistance)) {
22111
22874
  return 1;
22112
22875
  }
22113
22876
  try {
22114
- const mercator = maplibreGlExports.MercatorCoordinate.fromLngLat(
22877
+ const mercator = cachedMercator != null ? cachedMercator : maplibreGlExports.MercatorCoordinate.fromLngLat(
22115
22878
  { lng: location2.lng, lat: location2.lat },
22116
22879
  (_b = location2.z) != null ? _b : 0
22117
22880
  );
@@ -22220,14 +22983,14 @@ const applyAutoRotation = (sprite, nextLocation) => {
22220
22983
  return false;
22221
22984
  }
22222
22985
  const resolvedAngleRaw = isFiniteNumber(bearingDeg) ? bearingDeg : sprite.lastAutoRotationAngleDeg;
22223
- const resolvedAngle = normaliseAngleDeg(resolvedAngleRaw);
22986
+ const resolvedAngle = normalizeAngleDeg(resolvedAngleRaw);
22224
22987
  sprite.images.forEach((orderMap) => {
22225
22988
  orderMap.forEach((image) => {
22226
22989
  if (!image.autoRotation) {
22227
22990
  return;
22228
22991
  }
22229
22992
  image.resolvedBaseRotateDeg = resolvedAngle;
22230
- updateImageDisplayedRotation(image);
22993
+ syncImageRotationChannel(image);
22231
22994
  });
22232
22995
  });
22233
22996
  sprite.lastAutoRotationLocation = cloneSpriteLocation(nextLocation);
@@ -22244,10 +23007,59 @@ const QUAD_VERTEX_COUNT = 6;
22244
23007
  const VERTEX_SHADER_SOURCE = `
22245
23008
  attribute vec4 a_position;
22246
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;
22247
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
+ }
22248
23049
  void main() {
22249
23050
  v_uv = a_uv;
22250
- 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;
22251
23063
  }
22252
23064
  `;
22253
23065
  const FRAGMENT_SHADER_SOURCE = `
@@ -22266,6 +23078,175 @@ const INITIAL_QUAD_VERTICES = new Float32Array(
22266
23078
  const QUAD_VERTEX_SCRATCH = new Float32Array(
22267
23079
  QUAD_VERTEX_COUNT * VERTEX_COMPONENT_COUNT
22268
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
+ };
22269
23250
  const compileShader = (glContext, type, source) => {
22270
23251
  var _a;
22271
23252
  const shader = glContext.createShader(type);
@@ -22663,23 +23644,6 @@ const cloneOffset = (offset) => {
22663
23644
  offsetDeg: offset.offsetDeg
22664
23645
  };
22665
23646
  };
22666
- const updateImageDisplayedRotation = (image, optionsOverride) => {
22667
- const targetAngle = normaliseAngleDeg(
22668
- image.resolvedBaseRotateDeg + image.rotateDeg
22669
- );
22670
- const currentAngle = Number.isFinite(image.displayedRotateDeg) ? image.displayedRotateDeg : targetAngle;
22671
- const options = optionsOverride === void 0 ? image.rotationInterpolationOptions : optionsOverride;
22672
- const { nextAngleDeg, interpolationState } = resolveRotationTarget({
22673
- currentAngleDeg: currentAngle,
22674
- targetAngleDeg: targetAngle,
22675
- options: options != null ? options : void 0
22676
- });
22677
- image.displayedRotateDeg = nextAngleDeg;
22678
- image.rotationInterpolationState = interpolationState;
22679
- if (!interpolationState) {
22680
- image.displayedRotateDeg = targetAngle;
22681
- }
22682
- };
22683
23647
  const cloneInterpolationOptions = (options) => {
22684
23648
  return {
22685
23649
  mode: options.mode,
@@ -22687,40 +23651,40 @@ const cloneInterpolationOptions = (options) => {
22687
23651
  easing: options.easing
22688
23652
  };
22689
23653
  };
22690
- const cloneNumericInterpolationOptions = (options) => {
22691
- return {
22692
- durationMs: options.durationMs,
22693
- easing: options.easing
22694
- };
22695
- };
22696
23654
  const createImageStateFromInit = (imageInit, subLayer, order) => {
22697
23655
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
22698
23656
  const mode = (_a = imageInit.mode) != null ? _a : "surface";
22699
23657
  const autoRotationDefault = mode === "surface";
23658
+ const initialOffset = cloneOffset(imageInit.offset);
23659
+ const initialRotateDeg = normalizeAngleDeg((_b = imageInit.rotateDeg) != null ? _b : 0);
22700
23660
  const state = {
22701
23661
  subLayer,
22702
23662
  order,
22703
23663
  imageId: imageInit.imageId,
22704
23664
  mode,
22705
- opacity: (_b = imageInit.opacity) != null ? _b : 1,
22706
- scale: (_c = imageInit.scale) != null ? _c : 1,
23665
+ opacity: (_c = imageInit.opacity) != null ? _c : 1,
23666
+ scale: (_d = imageInit.scale) != null ? _d : 1,
22707
23667
  anchor: cloneAnchor(imageInit.anchor),
22708
- offset: cloneOffset(imageInit.offset),
22709
- rotateDeg: (_d = imageInit.rotateDeg) != null ? _d : 0,
22710
- displayedRotateDeg: normaliseAngleDeg((_e = imageInit.rotateDeg) != null ? _e : 0),
23668
+ offset: initialOffset,
23669
+ rotateDeg: (_e = imageInit.rotateDeg) != null ? _e : 0,
23670
+ displayedRotateDeg: initialRotateDeg,
22711
23671
  autoRotation: (_f = imageInit.autoRotation) != null ? _f : autoRotationDefault,
22712
23672
  autoRotationMinDistanceMeters: (_g = imageInit.autoRotationMinDistanceMeters) != null ? _g : DEFAULT_AUTO_ROTATION_MIN_DISTANCE_METERS,
22713
23673
  resolvedBaseRotateDeg: 0,
22714
23674
  originLocation: cloneOriginLocation(imageInit.originLocation),
22715
23675
  rotationInterpolationState: null,
22716
23676
  rotationInterpolationOptions: null,
22717
- offsetInterpolationState: null
23677
+ offsetDegInterpolationState: null,
23678
+ offsetMetersInterpolationState: null,
23679
+ lastCommandRotateDeg: initialRotateDeg,
23680
+ lastCommandOffsetDeg: initialOffset.offsetDeg,
23681
+ lastCommandOffsetMeters: initialOffset.offsetMeters
22718
23682
  };
22719
- const rotateInitOption = (_i = (_h = imageInit.rotationInterpolation) == null ? void 0 : _h.rotateDeg) != null ? _i : null;
23683
+ const rotateInitOption = (_i = (_h = imageInit.interpolation) == null ? void 0 : _h.rotateDeg) != null ? _i : null;
22720
23684
  if (rotateInitOption) {
22721
- state.rotationInterpolationOptions = cloneNumericInterpolationOptions(rotateInitOption);
23685
+ state.rotationInterpolationOptions = cloneInterpolationOptions(rotateInitOption);
22722
23686
  }
22723
- updateImageDisplayedRotation(state);
23687
+ syncImageRotationChannel(state);
22724
23688
  return state;
22725
23689
  };
22726
23690
  const createSpriteLayer = (options) => {
@@ -22730,6 +23694,7 @@ const createSpriteLayer = (options) => {
22730
23694
  const resolvedTextureFiltering = resolveTextureFilteringOptions(
22731
23695
  options == null ? void 0 : options.textureFiltering
22732
23696
  );
23697
+ const showDebugBounds = (options == null ? void 0 : options.showDebugBounds) === true;
22733
23698
  let gl = null;
22734
23699
  let map = null;
22735
23700
  let program = null;
@@ -22738,8 +23703,27 @@ const createSpriteLayer = (options) => {
22738
23703
  let attribUvLocation = -1;
22739
23704
  let uniformTextureLocation = null;
22740
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;
22741
23719
  let anisotropyExtension = null;
22742
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;
22743
23727
  const images = /* @__PURE__ */ new Map();
22744
23728
  const queuedTextureIds = /* @__PURE__ */ new Set();
22745
23729
  const queueTextureUpload = (image) => {
@@ -22752,6 +23736,347 @@ const createSpriteLayer = (options) => {
22752
23736
  queuedTextureIds.clear();
22753
23737
  };
22754
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;
23999
+ };
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);
24072
+ };
24073
+ const refreshSpriteHitTestBounds = (sprite) => {
24074
+ sprite.images.forEach((orderMap) => {
24075
+ orderMap.forEach((image) => {
24076
+ registerImageBoundsInHitTestTree(sprite, image);
24077
+ });
24078
+ });
24079
+ };
22755
24080
  const getImageState = (sprite, subLayer, order) => {
22756
24081
  var _a2;
22757
24082
  return (
@@ -22784,34 +24109,38 @@ const createSpriteLayer = (options) => {
22784
24109
  return deleted;
22785
24110
  };
22786
24111
  const HIT_TEST_EPSILON = 1e-3;
22787
- const pointInTriangle = (point, a, b, c) => {
22788
- const v0x = c.x - a.x;
22789
- const v0y = c.y - a.y;
22790
- const v1x = b.x - a.x;
22791
- const v1y = b.y - a.y;
22792
- const v2x = point.x - a.x;
22793
- const v2y = point.y - a.y;
22794
- const dot00 = v0x * v0x + v0y * v0y;
22795
- const dot01 = v0x * v1x + v0y * v1y;
22796
- const dot02 = v0x * v2x + v0y * v2y;
22797
- const dot11 = v1x * v1x + v1y * v1y;
22798
- const dot12 = v1x * v2x + v1y * v2y;
22799
- const denom = dot00 * dot11 - dot01 * dot01;
22800
- if (Math.abs(denom) < HIT_TEST_EPSILON) {
22801
- 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
+ }
22802
24136
  }
22803
- const invDenom = 1 / denom;
22804
- const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
22805
- const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
22806
- const w = 1 - u - v;
22807
- return u >= -HIT_TEST_EPSILON && v >= -HIT_TEST_EPSILON && w >= -HIT_TEST_EPSILON;
24137
+ return true;
22808
24138
  };
22809
- const pointInQuad = (point, corners) => pointInTriangle(point, corners[0], corners[1], corners[2]) || pointInTriangle(point, corners[0], corners[2], corners[3]);
22810
24139
  const isPointInsideHitEntry = (entry, point) => {
22811
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) {
22812
24141
  return false;
22813
24142
  }
22814
- return pointInQuad(point, entry.corners);
24143
+ return pointInRenderedQuad(point, entry.corners);
22815
24144
  };
22816
24145
  const buildSortedSubLayerBuckets = (entries) => {
22817
24146
  const buckets = /* @__PURE__ */ new Map();
@@ -22870,7 +24199,7 @@ const createSpriteLayer = (options) => {
22870
24199
  baseY = refCenter.y;
22871
24200
  }
22872
24201
  }
22873
- const totalRotDeg = Number.isFinite(img.displayedRotateDeg) ? img.displayedRotateDeg : normaliseAngleDeg(
24202
+ const totalRotDeg = Number.isFinite(img.displayedRotateDeg) ? img.displayedRotateDeg : normalizeAngleDeg(
22874
24203
  ((_d = img.resolvedBaseRotateDeg) != null ? _d : 0) + ((_e = img.rotateDeg) != null ? _e : 0)
22875
24204
  );
22876
24205
  const imageScaleLocal = (_f = img.scale) != null ? _f : 1;
@@ -22953,6 +24282,7 @@ const createSpriteLayer = (options) => {
22953
24282
  };
22954
24283
  const renderTargetEntries = [];
22955
24284
  const hitTestEntries = [];
24285
+ let hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
22956
24286
  const ensureHitTestCorners = (imageEntry) => {
22957
24287
  if (!imageEntry.hitTestCorners) {
22958
24288
  imageEntry.hitTestCorners = [
@@ -22964,7 +24294,10 @@ const createSpriteLayer = (options) => {
22964
24294
  }
22965
24295
  return imageEntry.hitTestCorners;
22966
24296
  };
22967
- const registerHitTestEntry = (spriteEntry, imageEntry, screenCorners) => {
24297
+ const registerHitTestEntry = (spriteEntry, imageEntry, screenCorners, drawIndex) => {
24298
+ if (!isHitTestEnabled) {
24299
+ return;
24300
+ }
22968
24301
  const corners = screenCorners;
22969
24302
  let minX = corners[0].x;
22970
24303
  let maxX = corners[0].x;
@@ -22977,7 +24310,7 @@ const createSpriteLayer = (options) => {
22977
24310
  if (corner.y < minY) minY = corner.y;
22978
24311
  if (corner.y > maxY) maxY = corner.y;
22979
24312
  }
22980
- hitTestEntries.push({
24313
+ const entry = {
22981
24314
  sprite: spriteEntry,
22982
24315
  image: imageEntry,
22983
24316
  corners,
@@ -22985,16 +24318,82 @@ const createSpriteLayer = (options) => {
22985
24318
  maxX,
22986
24319
  minY,
22987
24320
  maxY
22988
- });
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
+ }
22989
24328
  };
22990
- const findTopmostHitEntry = (point) => {
24329
+ const findTopmostHitEntryLinear = (point) => {
22991
24330
  for (let i = hitTestEntries.length - 1; i >= 0; i--) {
22992
24331
  const entry = hitTestEntries[i];
22993
24332
  if (isPointInsideHitEntry(entry, point)) {
22994
24333
  return entry;
22995
24334
  }
22996
24335
  }
22997
- return null;
24336
+ return null;
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);
22998
24397
  };
22999
24398
  const eventListeners = /* @__PURE__ */ new Map();
23000
24399
  const getListenerSet = (type) => {
@@ -23020,13 +24419,15 @@ const createSpriteLayer = (options) => {
23020
24419
  };
23021
24420
  let canvasElement = null;
23022
24421
  const inputListenerDisposers = [];
23023
- const hasSpriteClickListeners = () => {
24422
+ const hasSpriteListeners = (type) => {
23024
24423
  var _a2, _b;
23025
24424
  return (
23026
24425
  // Treat missing listener sets as zero, otherwise check the registered count.
23027
- ((_b = (_a2 = eventListeners.get("spriteclick")) == null ? void 0 : _a2.size) != null ? _b : 0) > 0
24426
+ ((_b = (_a2 = eventListeners.get(type)) == null ? void 0 : _a2.size) != null ? _b : 0) > 0
23028
24427
  );
23029
24428
  };
24429
+ const hasSpriteClickListeners = () => hasSpriteListeners("spriteclick");
24430
+ const hasSpriteHoverListeners = () => hasSpriteListeners("spritehover");
23030
24431
  const resolveScreenPointFromEvent = (nativeEvent) => {
23031
24432
  var _a2, _b, _c, _d;
23032
24433
  if (!canvasElement) {
@@ -23051,20 +24452,31 @@ const createSpriteLayer = (options) => {
23051
24452
  const mouseLike = nativeEvent;
23052
24453
  return toScreenPoint(mouseLike.clientX, mouseLike.clientY);
23053
24454
  };
24455
+ const resolveSpriteEventPayload = (hitEntry) => {
24456
+ var _a2, _b;
24457
+ if (!hitEntry) {
24458
+ return {
24459
+ sprite: void 0,
24460
+ image: void 0
24461
+ };
24462
+ }
24463
+ const spriteState = getSpriteState(hitEntry.sprite.spriteId);
24464
+ const imageState = (_b = (_a2 = spriteState == null ? void 0 : spriteState.images.get(hitEntry.image.subLayer)) == null ? void 0 : _a2.get(hitEntry.image.order)) != null ? _b : void 0;
24465
+ return {
24466
+ sprite: spriteState,
24467
+ image: imageState
24468
+ };
24469
+ };
23054
24470
  const dispatchSpriteClick = (hitEntry, screenPoint, originalEvent) => {
23055
24471
  const listeners = eventListeners.get("spriteclick");
23056
24472
  if (!listeners || listeners.size === 0) {
23057
24473
  return;
23058
24474
  }
23059
- const spriteState = getSpriteState(hitEntry.sprite.spriteId);
23060
- if (!spriteState) {
23061
- return;
23062
- }
23063
- const imageState = hitEntry.image;
24475
+ const payload = resolveSpriteEventPayload(hitEntry);
23064
24476
  const clickEvent = {
23065
24477
  type: "spriteclick",
23066
- sprite: spriteState,
23067
- image: imageState,
24478
+ sprite: payload.sprite,
24479
+ image: payload.image,
23068
24480
  screenPoint,
23069
24481
  originalEvent
23070
24482
  };
@@ -23072,22 +24484,50 @@ const createSpriteLayer = (options) => {
23072
24484
  listener(clickEvent);
23073
24485
  });
23074
24486
  };
23075
- const processInteractionEvent = (nativeEvent) => {
24487
+ const dispatchSpriteHover = (hitEntry, screenPoint, originalEvent) => {
24488
+ const listeners = eventListeners.get("spritehover");
24489
+ if (!listeners || listeners.size === 0) {
24490
+ return;
24491
+ }
24492
+ const payload = resolveSpriteEventPayload(hitEntry);
24493
+ const hoverEvent = {
24494
+ type: "spritehover",
24495
+ sprite: payload.sprite,
24496
+ image: payload.image,
24497
+ screenPoint,
24498
+ originalEvent
24499
+ };
24500
+ listeners.forEach((listener) => {
24501
+ listener(hoverEvent);
24502
+ });
24503
+ };
24504
+ const resolveHitTestResult = (nativeEvent) => {
24505
+ const screenPoint = resolveScreenPointFromEvent(nativeEvent);
24506
+ if (!screenPoint) {
24507
+ return null;
24508
+ }
24509
+ const hitEntry = findTopmostHitEntry(screenPoint);
24510
+ return { hitEntry: hitEntry != null ? hitEntry : null, screenPoint };
24511
+ };
24512
+ const processClickEvent = (nativeEvent) => {
23076
24513
  if (!hasSpriteClickListeners()) {
23077
24514
  return;
23078
24515
  }
23079
- if (hitTestEntries.length === 0) {
24516
+ const hitResult = resolveHitTestResult(nativeEvent);
24517
+ if (!hitResult || !hitResult.hitEntry) {
23080
24518
  return;
23081
24519
  }
23082
- const screenPoint = resolveScreenPointFromEvent(nativeEvent);
23083
- if (!screenPoint) {
24520
+ dispatchSpriteClick(hitResult.hitEntry, hitResult.screenPoint, nativeEvent);
24521
+ };
24522
+ const processHoverEvent = (nativeEvent) => {
24523
+ if (!hasSpriteHoverListeners()) {
23084
24524
  return;
23085
24525
  }
23086
- const hitEntry = findTopmostHitEntry(screenPoint);
23087
- if (!hitEntry) {
24526
+ const hitResult = resolveHitTestResult(nativeEvent);
24527
+ if (!hitResult) {
23088
24528
  return;
23089
24529
  }
23090
- dispatchSpriteClick(hitEntry, screenPoint, nativeEvent);
24530
+ dispatchSpriteHover(hitResult.hitEntry, hitResult.screenPoint, nativeEvent);
23091
24531
  };
23092
24532
  const ensureTextures = () => {
23093
24533
  if (!gl) {
@@ -23240,24 +24680,42 @@ const createSpriteLayer = (options) => {
23240
24680
  const supportsPointerEvents = typeof window !== "undefined" && "PointerEvent" in window;
23241
24681
  if (canvasElement) {
23242
24682
  if (supportsPointerEvents) {
23243
- const pointerListener = (event) => {
24683
+ const pointerUpListener = (event) => {
23244
24684
  if (event.pointerType === "mouse" && event.button !== 0) {
23245
24685
  return;
23246
24686
  }
23247
- processInteractionEvent(event);
24687
+ processClickEvent(event);
24688
+ };
24689
+ canvasElement.addEventListener("pointerup", pointerUpListener, {
24690
+ passive: true
24691
+ });
24692
+ registerDisposer(() => {
24693
+ canvasElement == null ? void 0 : canvasElement.removeEventListener("pointerup", pointerUpListener);
24694
+ });
24695
+ const pointerMoveListener = (event) => {
24696
+ if (!event.isPrimary) {
24697
+ return;
24698
+ }
24699
+ if (event.pointerType === "touch") {
24700
+ return;
24701
+ }
24702
+ processHoverEvent(event);
23248
24703
  };
23249
- canvasElement.addEventListener("pointerup", pointerListener, {
24704
+ canvasElement.addEventListener("pointermove", pointerMoveListener, {
23250
24705
  passive: true
23251
24706
  });
23252
24707
  registerDisposer(() => {
23253
- canvasElement == null ? void 0 : canvasElement.removeEventListener("pointerup", pointerListener);
24708
+ canvasElement == null ? void 0 : canvasElement.removeEventListener(
24709
+ "pointermove",
24710
+ pointerMoveListener
24711
+ );
23254
24712
  });
23255
24713
  } else {
23256
24714
  const clickListener = (event) => {
23257
24715
  if (event.button !== 0) {
23258
24716
  return;
23259
24717
  }
23260
- processInteractionEvent(event);
24718
+ processClickEvent(event);
23261
24719
  };
23262
24720
  canvasElement.addEventListener("click", clickListener, {
23263
24721
  passive: true
@@ -23266,7 +24724,7 @@ const createSpriteLayer = (options) => {
23266
24724
  canvasElement == null ? void 0 : canvasElement.removeEventListener("click", clickListener);
23267
24725
  });
23268
24726
  const touchListener = (event) => {
23269
- processInteractionEvent(event);
24727
+ processClickEvent(event);
23270
24728
  };
23271
24729
  canvasElement.addEventListener("touchend", touchListener, {
23272
24730
  passive: true
@@ -23274,6 +24732,15 @@ const createSpriteLayer = (options) => {
23274
24732
  registerDisposer(() => {
23275
24733
  canvasElement == null ? void 0 : canvasElement.removeEventListener("touchend", touchListener);
23276
24734
  });
24735
+ const mouseMoveListener = (event) => {
24736
+ processHoverEvent(event);
24737
+ };
24738
+ canvasElement.addEventListener("mousemove", mouseMoveListener, {
24739
+ passive: true
24740
+ });
24741
+ registerDisposer(() => {
24742
+ canvasElement == null ? void 0 : canvasElement.removeEventListener("mousemove", mouseMoveListener);
24743
+ });
23277
24744
  }
23278
24745
  }
23279
24746
  const buffer = glContext.createBuffer();
@@ -23310,7 +24777,59 @@ const createSpriteLayer = (options) => {
23310
24777
  shaderProgram,
23311
24778
  "u_opacity"
23312
24779
  );
23313
- 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) {
23314
24833
  throw new Error("Failed to acquire uniform locations.");
23315
24834
  }
23316
24835
  glContext.enableVertexAttribArray(attribPositionLocation);
@@ -23333,7 +24852,80 @@ const createSpriteLayer = (options) => {
23333
24852
  );
23334
24853
  glContext.uniform1i(uniformTextureLocation, 0);
23335
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);
23336
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
+ }
23337
24929
  scheduleRender();
23338
24930
  };
23339
24931
  const onRemove = () => {
@@ -23341,6 +24933,9 @@ const createSpriteLayer = (options) => {
23341
24933
  inputListenerDisposers.length = 0;
23342
24934
  canvasElement = null;
23343
24935
  hitTestEntries.length = 0;
24936
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24937
+ hitTestTree.clear();
24938
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
23344
24939
  const glContext = gl;
23345
24940
  if (glContext) {
23346
24941
  images.forEach((image) => {
@@ -23353,9 +24948,15 @@ const createSpriteLayer = (options) => {
23353
24948
  if (vertexBuffer) {
23354
24949
  glContext.deleteBuffer(vertexBuffer);
23355
24950
  }
24951
+ if (debugVertexBuffer) {
24952
+ glContext.deleteBuffer(debugVertexBuffer);
24953
+ }
23356
24954
  if (program) {
23357
24955
  glContext.deleteProgram(program);
23358
24956
  }
24957
+ if (debugProgram) {
24958
+ glContext.deleteProgram(debugProgram);
24959
+ }
23359
24960
  }
23360
24961
  eventListeners.forEach((set) => set.clear());
23361
24962
  eventListeners.clear();
@@ -23363,20 +24964,36 @@ const createSpriteLayer = (options) => {
23363
24964
  map = null;
23364
24965
  program = null;
23365
24966
  vertexBuffer = null;
24967
+ debugProgram = null;
24968
+ debugVertexBuffer = null;
23366
24969
  attribPositionLocation = -1;
23367
24970
  attribUvLocation = -1;
24971
+ debugAttribPositionLocation = -1;
23368
24972
  uniformTextureLocation = null;
23369
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;
23370
24986
  anisotropyExtension = null;
23371
24987
  maxSupportedAnisotropy = 1;
23372
24988
  };
23373
24989
  const render = (glContext, _options) => {
23374
24990
  hitTestEntries.length = 0;
24991
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
23375
24992
  const mapInstance = map;
23376
24993
  if (!mapInstance || !program || !vertexBuffer) {
23377
24994
  return;
23378
24995
  }
23379
- if (!uniformOpacityLocation || !uniformTextureLocation) {
24996
+ if (!uniformOpacityLocation || !uniformTextureLocation || !uniformScreenToClipScaleLocation || !uniformScreenToClipOffsetLocation) {
23380
24997
  return;
23381
24998
  }
23382
24999
  const timestamp = typeof performance !== "undefined" && typeof performance.now === "function" ? (
@@ -23409,41 +25026,8 @@ const createSpriteLayer = (options) => {
23409
25026
  }
23410
25027
  sprite.images.forEach((orderMap) => {
23411
25028
  orderMap.forEach((image) => {
23412
- const rotationState = image.rotationInterpolationState;
23413
- if (rotationState) {
23414
- const evaluation = evaluateNumericInterpolation({
23415
- state: rotationState,
23416
- timestamp
23417
- });
23418
- if (rotationState.startTimestamp < 0) {
23419
- rotationState.startTimestamp = evaluation.effectiveStartTimestamp;
23420
- }
23421
- image.displayedRotateDeg = normaliseAngleDeg(evaluation.value);
23422
- if (evaluation.completed) {
23423
- image.displayedRotateDeg = normaliseAngleDeg(
23424
- rotationState.finalValue
23425
- );
23426
- image.rotationInterpolationState = null;
23427
- } else {
23428
- hasActiveInterpolation = true;
23429
- }
23430
- }
23431
- const offsetState = image.offsetInterpolationState;
23432
- if (offsetState) {
23433
- const evaluation = evaluateNumericInterpolation({
23434
- state: offsetState,
23435
- timestamp
23436
- });
23437
- if (offsetState.startTimestamp < 0) {
23438
- offsetState.startTimestamp = evaluation.effectiveStartTimestamp;
23439
- }
23440
- image.offset.offsetDeg = evaluation.value;
23441
- if (evaluation.completed) {
23442
- image.offset.offsetDeg = offsetState.finalValue;
23443
- image.offsetInterpolationState = null;
23444
- } else {
23445
- hasActiveInterpolation = true;
23446
- }
25029
+ if (stepSpriteImageInterpolations(image, timestamp)) {
25030
+ hasActiveInterpolation = true;
23447
25031
  }
23448
25032
  });
23449
25033
  });
@@ -23461,6 +25045,17 @@ const createSpriteLayer = (options) => {
23461
25045
  const drawingBufferWidth = glContext.drawingBufferWidth;
23462
25046
  const drawingBufferHeight = glContext.drawingBufferHeight;
23463
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;
23464
25059
  const zoom = mapInstance.getZoom();
23465
25060
  const zoomScaleFactor = calculateZoomScaleFactor(zoom, resolvedScaling);
23466
25061
  const baseMetersPerPixel = resolvedScaling.metersPerPixel;
@@ -23495,18 +25090,84 @@ const createSpriteLayer = (options) => {
23495
25090
  UV_OFFSET
23496
25091
  );
23497
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;
23498
25156
  const drawSpriteImage = (spriteEntry, imageEntry, imageResource, originCenterCache2) => {
23499
- 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;
23500
25158
  let screenCornerBuffer = null;
23501
25159
  const anchor = (_a2 = imageEntry.anchor) != null ? _a2 : DEFAULT_ANCHOR;
23502
25160
  const offsetDef = (_b = imageEntry.offset) != null ? _b : DEFAULT_IMAGE_OFFSET;
23503
- const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normaliseAngleDeg(
25161
+ applySurfaceMode(false);
25162
+ applySurfaceClipUniforms(false, null);
25163
+ const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normalizeAngleDeg(
23504
25164
  ((_c = imageEntry.resolvedBaseRotateDeg) != null ? _c : 0) + ((_d = imageEntry.rotateDeg) != null ? _d : 0)
23505
25165
  );
23506
25166
  const projected = mapInstance.project(spriteEntry.currentLocation);
23507
25167
  if (!projected) {
23508
25168
  return;
23509
25169
  }
25170
+ const spriteMercator = resolveSpriteMercator(spriteEntry);
23510
25171
  const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
23511
25172
  zoom,
23512
25173
  spriteEntry.currentLocation.lat
@@ -23516,7 +25177,8 @@ const createSpriteLayer = (options) => {
23516
25177
  }
23517
25178
  const perspectiveRatio = calculatePerspectiveRatio(
23518
25179
  mapInstance,
23519
- spriteEntry.currentLocation
25180
+ spriteEntry.currentLocation,
25181
+ spriteMercator
23520
25182
  );
23521
25183
  const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
23522
25184
  metersPerPixelAtLat,
@@ -23526,6 +25188,7 @@ const createSpriteLayer = (options) => {
23526
25188
  return;
23527
25189
  }
23528
25190
  const imageScale = (_e = imageEntry.scale) != null ? _e : 1;
25191
+ const altitudeMeters = (_f = spriteEntry.currentLocation.z) != null ? _f : 0;
23529
25192
  const centerParams = {
23530
25193
  mapInstance,
23531
25194
  images,
@@ -23540,7 +25203,7 @@ const createSpriteLayer = (options) => {
23540
25203
  drawingBufferHeight,
23541
25204
  pixelRatio,
23542
25205
  clipContext,
23543
- altitudeMeters: (_f = spriteEntry.currentLocation.z) != null ? _f : 0
25206
+ altitudeMeters
23544
25207
  };
23545
25208
  let baseProjected = { x: projected.x, y: projected.y };
23546
25209
  if (imageEntry.originLocation !== void 0) {
@@ -23557,6 +25220,13 @@ const createSpriteLayer = (options) => {
23557
25220
  }
23558
25221
  }
23559
25222
  if (imageEntry.mode === "surface") {
25223
+ applyScreenToClipUniforms(
25224
+ identityScaleX,
25225
+ identityScaleY,
25226
+ identityOffsetX,
25227
+ identityOffsetY
25228
+ );
25229
+ imageEntry.surfaceShaderInputs = void 0;
23560
25230
  const baseLngLat = imageEntry.originLocation !== void 0 ? (
23561
25231
  // When an origin reference is set, reproject the cached screen point back to geographic space.
23562
25232
  mapInstance.unproject([
@@ -23584,7 +25254,7 @@ const createSpriteLayer = (options) => {
23584
25254
  drawingBufferWidth,
23585
25255
  drawingBufferHeight,
23586
25256
  pixelRatio,
23587
- altitudeMeters: (_i = spriteEntry.currentLocation.z) != null ? _i : 0,
25257
+ altitudeMeters,
23588
25258
  project: !clipContext ? (lngLat) => {
23589
25259
  const result = mapInstance.project(lngLat);
23590
25260
  return result ? { x: result.x, y: result.y } : null;
@@ -23593,6 +25263,9 @@ const createSpriteLayer = (options) => {
23593
25263
  if (!surfaceCenter.center) {
23594
25264
  return;
23595
25265
  }
25266
+ if (uniformBillboardModeLocation) {
25267
+ glContext.uniform1f(uniformBillboardModeLocation, 0);
25268
+ }
23596
25269
  const offsetMeters = calculateSurfaceOffsetMeters(
23597
25270
  offsetDef,
23598
25271
  imageScale,
@@ -23606,6 +25279,51 @@ const createSpriteLayer = (options) => {
23606
25279
  totalRotateDeg,
23607
25280
  offsetMeters
23608
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
+ }
23609
25327
  const hitTestCorners = ensureHitTestCorners(imageEntry);
23610
25328
  let bufferOffset = 0;
23611
25329
  for (const index of TRIANGLE_INDICES) {
@@ -23619,46 +25337,145 @@ const createSpriteLayer = (options) => {
23619
25337
  const clipPosition = projectLngLatToClipSpace(
23620
25338
  displaced.lng,
23621
25339
  displaced.lat,
23622
- // Default altitude to zero when sprites lack explicit elevation.
23623
- (_j = spriteEntry.currentLocation.z) != null ? _j : 0,
25340
+ altitudeMeters,
23624
25341
  clipContext
23625
25342
  );
23626
25343
  if (!clipPosition) {
23627
25344
  return;
23628
25345
  }
23629
- const screenCorner = clipToScreen(
23630
- clipPosition,
23631
- drawingBufferWidth,
23632
- drawingBufferHeight,
23633
- pixelRatio
23634
- );
23635
- if (!screenCorner) {
23636
- return;
23637
- }
23638
- const targetCorner = hitTestCorners[index];
23639
- targetCorner.x = screenCorner.x;
23640
- targetCorner.y = screenCorner.y;
23641
25346
  let [clipX, clipY, clipZ, clipW] = clipPosition;
23642
- {
23643
- const orderIndex = Math.min(imageEntry.order, ORDER_MAX - 1);
23644
- const biasIndex = imageEntry.subLayer * ORDER_BUCKET + orderIndex;
23645
- const biasNdc = -(biasIndex * EPS_NDC);
23646
- 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;
23647
25363
  const minClipZ = -clipW + MIN_CLIP_Z_EPSILON;
23648
25364
  if (clipZ < minClipZ) {
23649
25365
  clipZ = minClipZ;
23650
25366
  }
23651
25367
  }
25368
+ if (clipCornerPositions) {
25369
+ clipCornerPositions[index] = [clipX, clipY, clipZ, clipW];
25370
+ }
23652
25371
  const [u, v] = UV_CORNERS[index];
23653
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipX;
23654
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipY;
23655
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipZ;
23656
- 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
+ }
23657
25384
  QUAD_VERTEX_SCRATCH[bufferOffset++] = u;
23658
25385
  QUAD_VERTEX_SCRATCH[bufferOffset++] = v;
23659
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
+ }
23660
25469
  screenCornerBuffer = hitTestCorners;
23661
25470
  } else {
25471
+ applyScreenToClipUniforms(
25472
+ screenToClipScaleX,
25473
+ screenToClipScaleY,
25474
+ screenToClipOffsetX,
25475
+ screenToClipOffsetY
25476
+ );
25477
+ imageEntry.surfaceShaderInputs = void 0;
25478
+ applySurfaceMode(false);
23662
25479
  const placement = calculateBillboardCenterPosition({
23663
25480
  base: baseProjected,
23664
25481
  imageWidth: imageResource.width,
@@ -23673,46 +25490,95 @@ const createSpriteLayer = (options) => {
23673
25490
  anchor,
23674
25491
  offset: offsetDef
23675
25492
  });
23676
- const corners = calculateBillboardCornerScreenPositions({
25493
+ const billboardShaderInputs = {
23677
25494
  centerX: placement.centerX,
23678
25495
  centerY: placement.centerY,
23679
25496
  halfWidth: placement.halfWidth,
23680
25497
  halfHeight: placement.halfHeight,
23681
25498
  anchor,
23682
25499
  totalRotateDeg
23683
- });
23684
- const hitTestCorners = ensureHitTestCorners(imageEntry);
23685
- let bufferOffset = 0;
23686
- for (const index of TRIANGLE_INDICES) {
23687
- const corner = corners[index];
23688
- const [clipX, clipY] = screenToClip(
23689
- corner.x,
23690
- corner.y,
23691
- drawingBufferWidth,
23692
- drawingBufferHeight,
23693
- pixelRatio
25500
+ };
25501
+ if (uniformBillboardModeLocation) {
25502
+ glContext.uniform1f(
25503
+ uniformBillboardModeLocation,
25504
+ 1
23694
25505
  );
23695
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipX;
23696
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipY;
23697
- QUAD_VERTEX_SCRATCH[bufferOffset++] = 0;
23698
- QUAD_VERTEX_SCRATCH[bufferOffset++] = 1;
23699
- QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.u;
23700
- QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.v;
23701
- }
23702
- for (let i = 0; i < corners.length; i++) {
23703
- const source = corners[i];
23704
- const target = hitTestCorners[i];
23705
- target.x = source.x;
23706
- target.y = source.y;
23707
25506
  }
23708
- 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);
23709
25573
  }
23710
25574
  if (screenCornerBuffer && screenCornerBuffer.length === 4) {
23711
25575
  registerHitTestEntry(
23712
25576
  spriteEntry,
23713
25577
  imageEntry,
23714
- screenCornerBuffer
25578
+ screenCornerBuffer,
25579
+ drawOrderCounter
23715
25580
  );
25581
+ drawOrderCounter += 1;
23716
25582
  }
23717
25583
  glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, QUAD_VERTEX_SCRATCH);
23718
25584
  glContext.uniform1f(uniformOpacityLocation, imageEntry.opacity);
@@ -23742,6 +25608,7 @@ const createSpriteLayer = (options) => {
23742
25608
  if (!projected) {
23743
25609
  continue;
23744
25610
  }
25611
+ const spriteMercator = resolveSpriteMercator(spriteEntry);
23745
25612
  const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
23746
25613
  zoom,
23747
25614
  spriteEntry.currentLocation.lat
@@ -23751,7 +25618,8 @@ const createSpriteLayer = (options) => {
23751
25618
  }
23752
25619
  const perspectiveRatio = calculatePerspectiveRatio(
23753
25620
  mapInstance,
23754
- spriteEntry.currentLocation
25621
+ spriteEntry.currentLocation,
25622
+ spriteMercator
23755
25623
  );
23756
25624
  const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
23757
25625
  metersPerPixelAtLat,
@@ -23799,7 +25667,7 @@ const createSpriteLayer = (options) => {
23799
25667
  spriteMaxPixel
23800
25668
  }
23801
25669
  );
23802
- const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normaliseAngleDeg(
25670
+ const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normalizeAngleDeg(
23803
25671
  ((_e = imageEntry.resolvedBaseRotateDeg) != null ? _e : 0) + ((_f = imageEntry.rotateDeg) != null ? _f : 0)
23804
25672
  );
23805
25673
  const offsetMeters = calculateSurfaceOffsetMeters(
@@ -23914,13 +25782,83 @@ const createSpriteLayer = (options) => {
23914
25782
  for (const [, bucket] of sortedSubLayerBuckets) {
23915
25783
  renderSortedBucket(bucket);
23916
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
+ }
23917
25843
  glContext.depthMask(true);
23918
25844
  glContext.enable(glContext.DEPTH_TEST);
23919
25845
  glContext.disable(glContext.BLEND);
23920
25846
  scheduleRender();
23921
25847
  };
23922
- const registerImage = async (imageId, imageSource) => {
23923
- const bitmap = typeof imageSource === "string" ? await loadImageBitmap(imageSource) : imageSource;
25848
+ const registerImage = async (imageId, imageSource, options2) => {
25849
+ let bitmap;
25850
+ try {
25851
+ bitmap = typeof imageSource === "string" ? await loadImageBitmap(imageSource, options2) : imageSource;
25852
+ } catch (error) {
25853
+ if (error instanceof SvgSizeResolutionError) {
25854
+ console.warn(
25855
+ `[SpriteLayer] Unable to register image "${imageId}": ${error.message}`,
25856
+ error
25857
+ );
25858
+ return false;
25859
+ }
25860
+ throw error;
25861
+ }
23924
25862
  if (images.has(imageId)) {
23925
25863
  return false;
23926
25864
  }
@@ -24137,6 +26075,15 @@ const createSpriteLayer = (options) => {
24137
26075
  if (!image) {
24138
26076
  return false;
24139
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
+ });
24140
26087
  const glContext = gl;
24141
26088
  if (glContext && image.texture) {
24142
26089
  glContext.deleteTexture(image.texture);
@@ -24159,13 +26106,16 @@ const createSpriteLayer = (options) => {
24159
26106
  }
24160
26107
  });
24161
26108
  images.clear();
26109
+ hitTestTree.clear();
26110
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
26111
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24162
26112
  clearTextureQueue();
24163
26113
  ensureRenderTargetEntries();
24164
26114
  scheduleRender();
24165
26115
  };
24166
26116
  const getAllImageIds = () => Array.from(images.keys());
24167
26117
  const addSpriteInternal = (spriteId, init) => {
24168
- var _a2, _b, _c, _d;
26118
+ var _a2, _b, _c, _d, _e;
24169
26119
  if (sprites.get(spriteId)) {
24170
26120
  return false;
24171
26121
  }
@@ -24226,23 +26176,33 @@ const createSpriteLayer = (options) => {
24226
26176
  }
24227
26177
  }
24228
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
+ );
24229
26184
  const spriteState = {
24230
26185
  spriteId,
24231
26186
  // Sprites default to enabled unless explicitly disabled in the init payload.
24232
- isEnabled: (_c = init.isEnabled) != null ? _c : true,
26187
+ isEnabled: (_d = init.isEnabled) != null ? _d : true,
24233
26188
  currentLocation,
24234
26189
  fromLocation: void 0,
24235
26190
  toLocation: void 0,
24236
26191
  images: images2,
24237
26192
  // Tags default to null to simplify downstream comparisons.
24238
- tag: (_d = init.tag) != null ? _d : null,
26193
+ tag: (_e = init.tag) != null ? _e : null,
24239
26194
  interpolationState: null,
24240
26195
  pendingInterpolationOptions: null,
24241
26196
  lastCommandLocation: cloneSpriteLocation(currentLocation),
24242
26197
  lastAutoRotationLocation: cloneSpriteLocation(currentLocation),
24243
- lastAutoRotationAngleDeg: 0
26198
+ lastAutoRotationAngleDeg: 0,
26199
+ cachedMercator: initialMercator,
26200
+ cachedMercatorLng: currentLocation.lng,
26201
+ cachedMercatorLat: currentLocation.lat,
26202
+ cachedMercatorZ: initialAltitude
24244
26203
  };
24245
26204
  sprites.set(spriteId, spriteState);
26205
+ refreshSpriteHitTestBounds(spriteState);
24246
26206
  return true;
24247
26207
  };
24248
26208
  const resolveSpriteInitCollection = (collection) => {
@@ -24277,7 +26237,19 @@ const createSpriteLayer = (options) => {
24277
26237
  }
24278
26238
  return addedCount;
24279
26239
  };
24280
- 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
+ };
24281
26253
  const removeSprite = (spriteId) => {
24282
26254
  const removed = removeSpriteInternal(spriteId);
24283
26255
  if (!removed) {
@@ -24305,6 +26277,9 @@ const createSpriteLayer = (options) => {
24305
26277
  if (removedCount === 0) {
24306
26278
  return 0;
24307
26279
  }
26280
+ hitTestTree.clear();
26281
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
26282
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24308
26283
  sprites.clear();
24309
26284
  ensureRenderTargetEntries();
24310
26285
  scheduleRender();
@@ -24321,6 +26296,9 @@ const createSpriteLayer = (options) => {
24321
26296
  let removedCount = 0;
24322
26297
  sprite.images.forEach((orderMap) => {
24323
26298
  removedCount += orderMap.size;
26299
+ orderMap.forEach((image) => {
26300
+ removeImageBoundsFromHitTestTree(image);
26301
+ });
24324
26302
  });
24325
26303
  sprite.images.clear();
24326
26304
  ensureRenderTargetEntries();
@@ -24369,8 +26347,9 @@ const createSpriteLayer = (options) => {
24369
26347
  if (state.autoRotation) {
24370
26348
  state.resolvedBaseRotateDeg = sprite.lastAutoRotationAngleDeg;
24371
26349
  }
24372
- updateImageDisplayedRotation(state);
26350
+ syncImageRotationChannel(state);
24373
26351
  setImageState(sprite, state);
26352
+ registerImageBoundsInHitTestTree(sprite, state);
24374
26353
  resultOut.isUpdated = true;
24375
26354
  return true;
24376
26355
  };
@@ -24410,41 +26389,32 @@ const createSpriteLayer = (options) => {
24410
26389
  if (imageUpdate.anchor !== void 0) {
24411
26390
  state.anchor = cloneAnchor(imageUpdate.anchor);
24412
26391
  }
24413
- const rotationInterpolation = imageUpdate.rotationInterpolation;
24414
- const offsetInterpolationOption = rotationInterpolation == null ? void 0 : rotationInterpolation.offsetDeg;
24415
- const rotateInterpolationOption = rotationInterpolation == null ? void 0 : rotationInterpolation.rotateDeg;
26392
+ const interpolationOptions = imageUpdate.interpolation;
26393
+ const offsetDegInterpolationOption = interpolationOptions == null ? void 0 : interpolationOptions.offsetDeg;
26394
+ const offsetMetersInterpolationOption = interpolationOptions == null ? void 0 : interpolationOptions.offsetMeters;
26395
+ const rotateInterpolationOption = interpolationOptions == null ? void 0 : interpolationOptions.rotateDeg;
24416
26396
  let rotationOverride;
24417
26397
  let hasRotationOverride = false;
24418
26398
  if (imageUpdate.offset !== void 0) {
24419
- const newOffset = cloneOffset(imageUpdate.offset);
24420
- if (offsetInterpolationOption && offsetInterpolationOption.durationMs > 0) {
24421
- const { state: interpolationState, requiresInterpolation } = createNumericInterpolationState({
24422
- currentValue: state.offset.offsetDeg,
24423
- targetValue: newOffset.offsetDeg,
24424
- options: offsetInterpolationOption
24425
- });
24426
- if (requiresInterpolation) {
24427
- state.offset.offsetMeters = newOffset.offsetMeters;
24428
- state.offsetInterpolationState = interpolationState;
24429
- } else {
24430
- state.offset = newOffset;
24431
- state.offsetInterpolationState = null;
24432
- }
24433
- } else {
24434
- state.offset = newOffset;
24435
- state.offsetInterpolationState = null;
26399
+ const clonedOffset = cloneOffset(imageUpdate.offset);
26400
+ applyOffsetUpdate(state, clonedOffset, {
26401
+ deg: offsetDegInterpolationOption,
26402
+ meters: offsetMetersInterpolationOption
26403
+ });
26404
+ } else {
26405
+ if (offsetDegInterpolationOption === null) {
26406
+ clearOffsetDegInterpolation(state);
26407
+ }
26408
+ if (offsetMetersInterpolationOption === null) {
26409
+ clearOffsetMetersInterpolation(state);
24436
26410
  }
24437
- } else if (offsetInterpolationOption === null) {
24438
- state.offsetInterpolationState = null;
24439
26411
  }
24440
26412
  if (rotateInterpolationOption !== void 0) {
24441
26413
  if (rotateInterpolationOption === null) {
24442
26414
  state.rotationInterpolationOptions = null;
24443
26415
  rotationOverride = null;
24444
26416
  } else {
24445
- const cloned = cloneNumericInterpolationOptions(
24446
- rotateInterpolationOption
24447
- );
26417
+ const cloned = cloneInterpolationOptions(rotateInterpolationOption);
24448
26418
  state.rotationInterpolationOptions = cloned;
24449
26419
  rotationOverride = cloned;
24450
26420
  }
@@ -24486,12 +26456,13 @@ const createSpriteLayer = (options) => {
24486
26456
  }
24487
26457
  }
24488
26458
  if (requireRotationSync) {
24489
- updateImageDisplayedRotation(
26459
+ syncImageRotationChannel(
24490
26460
  state,
24491
26461
  // When a rotation override has been computed, pass it along (null clears interpolation); otherwise leave undefined.
24492
26462
  hasRotationOverride ? rotationOverride != null ? rotationOverride : null : void 0
24493
26463
  );
24494
26464
  }
26465
+ registerImageBoundsInHitTestTree(sprite, state);
24495
26466
  resultOut.isUpdated = true;
24496
26467
  return true;
24497
26468
  };
@@ -24510,6 +26481,10 @@ const createSpriteLayer = (options) => {
24510
26481
  return true;
24511
26482
  };
24512
26483
  const removeSpriteImageInternal = (sprite, subLayer, order, resultOut) => {
26484
+ const state = getImageState(sprite, subLayer, order);
26485
+ if (state) {
26486
+ removeImageBoundsFromHitTestTree(state);
26487
+ }
24513
26488
  const deleted = deleteImageState(sprite, subLayer, order);
24514
26489
  if (deleted) {
24515
26490
  resultOut.isUpdated = true;
@@ -24539,11 +26514,13 @@ const createSpriteLayer = (options) => {
24539
26514
  }
24540
26515
  let updated = false;
24541
26516
  let isRequiredRender = false;
26517
+ let needsHitTestRefresh = false;
24542
26518
  if (update.isEnabled !== void 0) {
24543
26519
  if (sprite.isEnabled !== update.isEnabled) {
24544
26520
  sprite.isEnabled = update.isEnabled;
24545
26521
  updated = true;
24546
26522
  isRequiredRender = true;
26523
+ needsHitTestRefresh = true;
24547
26524
  }
24548
26525
  }
24549
26526
  let interpolationOptionsForLocation = void 0;
@@ -24620,6 +26597,7 @@ const createSpriteLayer = (options) => {
24620
26597
  }
24621
26598
  sprite.pendingInterpolationOptions = null;
24622
26599
  applyAutoRotation(sprite, newCommandLocation);
26600
+ needsHitTestRefresh = true;
24623
26601
  }
24624
26602
  if (update.tag !== void 0) {
24625
26603
  const nextTag = (_a2 = update.tag) != null ? _a2 : null;
@@ -24628,6 +26606,9 @@ const createSpriteLayer = (options) => {
24628
26606
  updated = true;
24629
26607
  }
24630
26608
  }
26609
+ if (needsHitTestRefresh) {
26610
+ refreshSpriteHitTestBounds(sprite);
26611
+ }
24631
26612
  if (isRequiredRender) {
24632
26613
  return "isRequiredRender";
24633
26614
  }
@@ -24651,34 +26632,6 @@ const createSpriteLayer = (options) => {
24651
26632
  return true;
24652
26633
  }
24653
26634
  };
24654
- const updateBulk = (updateBulkList) => {
24655
- let updatedCount = 0;
24656
- let isRequiredRender = false;
24657
- updateBulkList.forEach((update) => {
24658
- const result = updateSpriteInternal(update.spriteId, update);
24659
- switch (result) {
24660
- case "notfound":
24661
- // Sprite missing; nothing to do for this entry.
24662
- case "ignored":
24663
- break;
24664
- case "updated":
24665
- updatedCount++;
24666
- break;
24667
- // When rendering must occur because of this update
24668
- case "isRequiredRender":
24669
- ensureRenderTargetEntries();
24670
- scheduleRender();
24671
- updatedCount++;
24672
- isRequiredRender = true;
24673
- break;
24674
- }
24675
- });
24676
- if (isRequiredRender) {
24677
- ensureRenderTargetEntries();
24678
- scheduleRender();
24679
- }
24680
- return updatedCount;
24681
- };
24682
26635
  const mutateSprites = (sourceItems, mutator) => {
24683
26636
  if (sourceItems.length === 0) {
24684
26637
  return 0;
@@ -24691,6 +26644,10 @@ const createSpriteLayer = (options) => {
24691
26644
  isUpdated: false
24692
26645
  };
24693
26646
  const updateObject = {
26647
+ isEnabled: void 0,
26648
+ location: void 0,
26649
+ interpolation: void 0,
26650
+ tag: void 0,
24694
26651
  getImageIndexMap: () => {
24695
26652
  const map2 = /* @__PURE__ */ new Map();
24696
26653
  currentSprite.images.forEach((inner, subLayer) => {
@@ -24884,9 +26841,23 @@ const createSpriteLayer = (options) => {
24884
26841
  updateSpriteImage,
24885
26842
  removeSpriteImage,
24886
26843
  updateSprite,
24887
- updateBulk,
24888
26844
  mutateSprites,
24889
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
+ },
24890
26861
  on: addEventListener2,
24891
26862
  off: removeEventListener2
24892
26863
  };
@@ -24896,6 +26867,7 @@ export {
24896
26867
  BETTER_TEXTURE_FILTERING_OPTIONS,
24897
26868
  DEFAULT_TEXTURE_FILTERING_OPTIONS,
24898
26869
  STANDARD_SPRITE_SCALING_OPTIONS,
26870
+ SvgSizeResolutionError,
24899
26871
  UNLIMITED_SPRITE_SCALING_OPTIONS,
24900
26872
  applyAutoRotation,
24901
26873
  calculatePerspectiveRatio,
@@ -24906,7 +26878,9 @@ export {
24906
26878
  createImageStateFromInit,
24907
26879
  createShaderProgram,
24908
26880
  createSpriteLayer,
26881
+ loadImageBitmap,
24909
26882
  multiplyMatrixAndVector,
24910
- projectLngLatToClipSpace
26883
+ projectLngLatToClipSpace,
26884
+ readImageBitmap
24911
26885
  };
24912
26886
  //# sourceMappingURL=index.mjs.map