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.cjs CHANGED
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
2
5
  /*!
3
6
  * name: maplibre-gl-layers
4
- * version: 0.6.0
7
+ * version: 0.11.0
5
8
  * description: MapLibre's layer extension library enabling the display, movement, and modification of large numbers of dynamic sprite images
6
9
  * author: Kouji Matsui (@kekyo@mi.kekyo.net)
7
10
  * license: MIT
8
11
  * repository.url: https://github.com/kekyo/maplibre-gl-layers.git
9
- * git.commit.hash: 481f544de02fd3e71a2ba6c28bbb7eeb98b49eff
12
+ * git.commit.hash: 371efb126f281333d59ec75cfe788d45f3b1482e
10
13
  */
11
14
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
12
15
  const UNLIMITED_SPRITE_SCALING_OPTIONS = {
@@ -39,6 +42,270 @@ const BETTER_TEXTURE_FILTERING_OPTIONS = {
39
42
  generateMipmaps: true,
40
43
  maxAnisotropy: 8
41
44
  };
45
+ class SvgSizeResolutionError extends Error {
46
+ constructor(message, code) {
47
+ super(message);
48
+ __publicField(this, "code");
49
+ this.name = "SvgSizeResolutionError";
50
+ this.code = code;
51
+ }
52
+ }
53
+ const parseNumericLength = (value) => {
54
+ if (!value) {
55
+ return void 0;
56
+ }
57
+ const trimmed = value.trim();
58
+ if (trimmed.length === 0) {
59
+ return void 0;
60
+ }
61
+ const parsed = Number.parseFloat(trimmed);
62
+ if (!Number.isFinite(parsed) || parsed <= 0) {
63
+ return void 0;
64
+ }
65
+ return parsed;
66
+ };
67
+ const extractStyleLength = (styleValue, property) => {
68
+ if (!styleValue) {
69
+ return void 0;
70
+ }
71
+ const declarations = styleValue.split(";").map((decl) => decl.trim()).filter((decl) => decl.length > 0);
72
+ for (const declaration of declarations) {
73
+ const [prop, rawValue] = declaration.split(":");
74
+ if (!prop || !rawValue) {
75
+ continue;
76
+ }
77
+ if (prop.trim().toLowerCase() === property) {
78
+ return parseNumericLength(rawValue);
79
+ }
80
+ }
81
+ return void 0;
82
+ };
83
+ const parseSvgSize = (svgText) => {
84
+ try {
85
+ const doc = new DOMParser().parseFromString(svgText, "image/svg+xml");
86
+ const svg = doc.documentElement;
87
+ if (!svg || svg.tagName.toLowerCase() !== "svg") {
88
+ return { hasViewBox: false };
89
+ }
90
+ const attrWidth = parseNumericLength(svg.getAttribute("width"));
91
+ const attrHeight = parseNumericLength(svg.getAttribute("height"));
92
+ const styleWidth = extractStyleLength(svg.getAttribute("style"), "width");
93
+ const styleHeight = extractStyleLength(svg.getAttribute("style"), "height");
94
+ const width = attrWidth != null ? attrWidth : styleWidth;
95
+ const height = attrHeight != null ? attrHeight : styleHeight;
96
+ let viewBoxWidth;
97
+ let viewBoxHeight;
98
+ let hasViewBox = false;
99
+ const viewBox = svg.getAttribute("viewBox");
100
+ if (viewBox) {
101
+ const parts = viewBox.split(/[\s,]+/).map((part) => Number.parseFloat(part)).filter((part) => Number.isFinite(part));
102
+ if (parts.length === 4) {
103
+ viewBoxWidth = parts[2];
104
+ viewBoxHeight = parts[3];
105
+ if (viewBoxWidth > 0 && viewBoxHeight > 0) {
106
+ hasViewBox = true;
107
+ } else {
108
+ viewBoxWidth = void 0;
109
+ viewBoxHeight = void 0;
110
+ }
111
+ }
112
+ }
113
+ return {
114
+ width: width !== void 0 && width > 0 ? width : void 0,
115
+ height: height !== void 0 && height > 0 ? height : void 0,
116
+ viewBoxWidth,
117
+ viewBoxHeight,
118
+ hasViewBox
119
+ };
120
+ } catch (e) {
121
+ return { hasViewBox: false };
122
+ }
123
+ };
124
+ const determineSvgRasterDimensions = (parsed, options) => {
125
+ var _a, _b;
126
+ const overrideWidth = options == null ? void 0 : options.width;
127
+ const overrideHeight = options == null ? void 0 : options.height;
128
+ if (overrideWidth !== void 0 && overrideHeight !== void 0 && overrideWidth > 0 && overrideHeight > 0) {
129
+ return {
130
+ width: Math.max(1, Math.round(overrideWidth)),
131
+ height: Math.max(1, Math.round(overrideHeight))
132
+ };
133
+ }
134
+ const intrinsicWidth = parsed == null ? void 0 : parsed.width;
135
+ const intrinsicHeight = parsed == null ? void 0 : parsed.height;
136
+ const hasValidViewBox = Boolean(
137
+ (parsed == null ? void 0 : parsed.hasViewBox) && parsed.viewBoxWidth !== void 0 && parsed.viewBoxHeight !== void 0 && parsed.viewBoxWidth > 0 && parsed.viewBoxHeight > 0
138
+ );
139
+ const viewBoxAspect = hasValidViewBox ? parsed.viewBoxWidth / parsed.viewBoxHeight : void 0;
140
+ let baseWidth;
141
+ let baseHeight;
142
+ let aspect = intrinsicWidth !== void 0 && intrinsicHeight !== void 0 && intrinsicHeight > 0 ? intrinsicWidth / intrinsicHeight : viewBoxAspect;
143
+ if (intrinsicWidth !== void 0 && intrinsicWidth > 0 && intrinsicHeight !== void 0 && intrinsicHeight > 0) {
144
+ baseWidth = intrinsicWidth;
145
+ baseHeight = intrinsicHeight;
146
+ } else if (intrinsicWidth !== void 0 && intrinsicWidth > 0 && aspect !== void 0) {
147
+ baseWidth = intrinsicWidth;
148
+ baseHeight = intrinsicWidth / aspect;
149
+ } else if (intrinsicHeight !== void 0 && intrinsicHeight > 0 && aspect !== void 0) {
150
+ baseHeight = intrinsicHeight;
151
+ baseWidth = intrinsicHeight * aspect;
152
+ } else if (hasValidViewBox && ((_a = options == null ? void 0 : options.svg) == null ? void 0 : _a.useViewBoxDimensions)) {
153
+ baseWidth = parsed.viewBoxWidth;
154
+ baseHeight = parsed.viewBoxHeight;
155
+ aspect = baseWidth / baseHeight;
156
+ }
157
+ if ((baseWidth === void 0 || baseHeight === void 0) && hasValidViewBox && !((_b = options == null ? void 0 : options.svg) == null ? void 0 : _b.useViewBoxDimensions)) {
158
+ throw new SvgSizeResolutionError(
159
+ "SVG width/height attributes are missing and useViewBoxDimensions option is disabled.",
160
+ "viewbox-disabled"
161
+ );
162
+ }
163
+ if (baseWidth === void 0 || baseHeight === void 0) {
164
+ throw new SvgSizeResolutionError(
165
+ "SVG image lacks sufficient sizing information.",
166
+ "size-missing"
167
+ );
168
+ }
169
+ aspect = aspect != null ? aspect : baseWidth / baseHeight;
170
+ let finalWidth = baseWidth;
171
+ let finalHeight = baseHeight;
172
+ if (overrideWidth !== void 0 && overrideWidth > 0) {
173
+ finalWidth = overrideWidth;
174
+ if (overrideHeight === void 0) {
175
+ if (aspect === void 0) {
176
+ throw new SvgSizeResolutionError(
177
+ "Unable to infer SVG height from width; aspect ratio is undefined.",
178
+ "invalid-dimensions"
179
+ );
180
+ }
181
+ finalHeight = finalWidth / aspect;
182
+ }
183
+ }
184
+ if (overrideHeight !== void 0 && overrideHeight > 0) {
185
+ finalHeight = overrideHeight;
186
+ if (overrideWidth === void 0) {
187
+ if (aspect === void 0) {
188
+ throw new SvgSizeResolutionError(
189
+ "Unable to infer SVG width from height; aspect ratio is undefined.",
190
+ "invalid-dimensions"
191
+ );
192
+ }
193
+ finalWidth = finalHeight * aspect;
194
+ }
195
+ }
196
+ if (!Number.isFinite(finalWidth) || !Number.isFinite(finalHeight) || finalWidth <= 0 || finalHeight <= 0) {
197
+ throw new SvgSizeResolutionError(
198
+ "Resolved SVG dimensions are invalid.",
199
+ "invalid-dimensions"
200
+ );
201
+ }
202
+ return {
203
+ width: Math.max(1, Math.round(finalWidth)),
204
+ height: Math.max(1, Math.round(finalHeight))
205
+ };
206
+ };
207
+ const isSvgMimeType = (mimeType) => {
208
+ if (!mimeType) {
209
+ return false;
210
+ }
211
+ return mimeType.toLowerCase().includes("image/svg");
212
+ };
213
+ const rasterizeSvgWithCanvas = async (blob, width, height, options) => {
214
+ if (typeof document === "undefined") {
215
+ throw new Error(
216
+ "SVG rasterization fallback requires a browser environment"
217
+ );
218
+ }
219
+ const blobUrl = URL.createObjectURL(blob);
220
+ try {
221
+ const image = await new Promise((resolve, reject) => {
222
+ const element = new Image();
223
+ element.onload = () => resolve(element);
224
+ element.onerror = () => reject(new Error("Failed to load SVG for rasterization"));
225
+ element.src = blobUrl;
226
+ });
227
+ const canvas = document.createElement("canvas");
228
+ canvas.width = width;
229
+ canvas.height = height;
230
+ const ctx = canvas.getContext("2d");
231
+ if (!ctx) {
232
+ throw new Error("Failed to acquire 2D context for SVG rasterization");
233
+ }
234
+ ctx.clearRect(0, 0, width, height);
235
+ ctx.imageSmoothingEnabled = true;
236
+ if ((options == null ? void 0 : options.resizeQuality) === "pixelated") {
237
+ ctx.imageSmoothingEnabled = false;
238
+ } else if (options == null ? void 0 : options.resizeQuality) {
239
+ ctx.imageSmoothingQuality = options.resizeQuality;
240
+ }
241
+ ctx.drawImage(image, 0, 0, width, height);
242
+ try {
243
+ return await createImageBitmap(canvas);
244
+ } catch (e) {
245
+ const canvasBlob = await new Promise((resolve, reject) => {
246
+ canvas.toBlob((result) => {
247
+ if (result) {
248
+ resolve(result);
249
+ } else {
250
+ reject(
251
+ new Error("Failed to convert canvas to blob during rasterization")
252
+ );
253
+ }
254
+ });
255
+ });
256
+ return await createImageBitmap(canvasBlob);
257
+ }
258
+ } finally {
259
+ URL.revokeObjectURL(blobUrl);
260
+ }
261
+ };
262
+ const resolveSvgBitmapWithFallback = async (blob, width, height, options) => {
263
+ const bitmapOptions = {
264
+ resizeWidth: width,
265
+ resizeHeight: height
266
+ };
267
+ if (options == null ? void 0 : options.resizeQuality) {
268
+ bitmapOptions.resizeQuality = options.resizeQuality;
269
+ }
270
+ try {
271
+ return await createImageBitmap(blob, bitmapOptions);
272
+ } catch (error) {
273
+ return await rasterizeSvgWithCanvas(blob, width, height, options);
274
+ }
275
+ };
276
+ const internalReadImageBitmap = async (blob, shouldTreatAsSvg, options) => {
277
+ const svgOptions = options == null ? void 0 : options.svg;
278
+ if (shouldTreatAsSvg) {
279
+ let parsed = null;
280
+ if ((svgOptions == null ? void 0 : svgOptions.inspectSize) !== false) {
281
+ const text = await blob.text();
282
+ parsed = parseSvgSize(text);
283
+ }
284
+ const { width, height } = determineSvgRasterDimensions(parsed, options);
285
+ return await resolveSvgBitmapWithFallback(blob, width, height, options);
286
+ }
287
+ return await createImageBitmap(blob, {
288
+ resizeWidth: options == null ? void 0 : options.width,
289
+ resizeHeight: options == null ? void 0 : options.height,
290
+ resizeQuality: options == null ? void 0 : options.resizeQuality
291
+ });
292
+ };
293
+ const readImageBitmap = (blob, options) => {
294
+ const svgOptions = options == null ? void 0 : options.svg;
295
+ const shouldTreatAsSvg = (svgOptions == null ? void 0 : svgOptions.assumeSvg) === true;
296
+ return internalReadImageBitmap(blob, shouldTreatAsSvg, options);
297
+ };
298
+ const loadImageBitmap = async (url, options) => {
299
+ var _a;
300
+ const response = await fetch(url);
301
+ if (!response.ok) {
302
+ throw new Error(`Failed to fetch image from ${url}`);
303
+ }
304
+ const mimeType = response.headers.get("content-type");
305
+ const blob = await response.blob();
306
+ const shouldTreatAsSvg = ((_a = options == null ? void 0 : options.svg) == null ? void 0 : _a.assumeSvg) === true || isSvgMimeType(mimeType);
307
+ return await internalReadImageBitmap(blob, shouldTreatAsSvg, options);
308
+ };
42
309
  var maplibreGl$1 = { exports: {} };
43
310
  /**
44
311
  * MapLibre GL JS
@@ -21113,14 +21380,6 @@ function transformMat4(out, a, m) {
21113
21380
  return a;
21114
21381
  };
21115
21382
  })();
21116
- const loadImageBitmap = async (url) => {
21117
- const response = await fetch(url);
21118
- if (!response.ok) {
21119
- throw new Error(`Failed to fetch image from ${url}`);
21120
- }
21121
- const blob = await response.blob();
21122
- return await createImageBitmap(blob);
21123
- };
21124
21383
  const cloneSpriteLocation = (location2) => {
21125
21384
  if (location2.z === void 0) {
21126
21385
  return { lng: location2.lng, lat: location2.lat };
@@ -21177,7 +21436,7 @@ const computeFeedforwardTarget = (previous, next) => {
21177
21436
  }
21178
21437
  return target;
21179
21438
  };
21180
- const normaliseOptions$1 = (options) => {
21439
+ const normalizeOptions$2 = (options) => {
21181
21440
  var _a;
21182
21441
  return {
21183
21442
  durationMs: Math.max(0, options.durationMs),
@@ -21187,7 +21446,7 @@ const normaliseOptions$1 = (options) => {
21187
21446
  };
21188
21447
  const createInterpolationState = (params) => {
21189
21448
  const { currentLocation, lastCommandLocation, nextCommandLocation } = params;
21190
- const options = normaliseOptions$1(params.options);
21449
+ const options = normalizeOptions$2(params.options);
21191
21450
  const from = cloneSpriteLocation(currentLocation);
21192
21451
  const easing = resolveEasing(options.easing);
21193
21452
  let to;
@@ -21232,8 +21491,8 @@ const evaluateInterpolation = (params) => {
21232
21491
  };
21233
21492
  };
21234
21493
  const NUMERIC_EPSILON = 1e-6;
21235
- const normaliseDuration = (durationMs) => Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0;
21236
- const normaliseDelta = (delta) => {
21494
+ const normalizeDuration$1 = (durationMs) => Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0;
21495
+ const normalizeDelta = (delta) => {
21237
21496
  if (!Number.isFinite(delta)) {
21238
21497
  return 0;
21239
21498
  }
@@ -21245,16 +21504,24 @@ const normaliseDelta = (delta) => {
21245
21504
  }
21246
21505
  return adjusted;
21247
21506
  };
21248
- const normaliseOptions = (options) => {
21507
+ const normalizeOptions$1 = (options) => {
21508
+ var _a;
21249
21509
  return {
21250
- durationMs: normaliseDuration(options.durationMs),
21251
- easing: resolveEasing(options.easing)
21510
+ durationMs: normalizeDuration$1(options.durationMs),
21511
+ easing: resolveEasing(options.easing),
21512
+ mode: (_a = options.mode) != null ? _a : "feedback"
21252
21513
  };
21253
21514
  };
21254
- const createNumericInterpolationState = (params) => {
21515
+ const createDegreeInterpolationState = (params) => {
21255
21516
  const { currentValue, targetValue } = params;
21256
- const options = normaliseOptions(params.options);
21257
- const delta = normaliseDelta(targetValue - currentValue);
21517
+ const options = normalizeOptions$1(params.options);
21518
+ let effectiveTarget = targetValue;
21519
+ const previousCommand = params.previousCommandValue;
21520
+ if (options.mode === "feedforward" && previousCommand !== void 0 && Number.isFinite(previousCommand)) {
21521
+ const commandDelta = normalizeDelta(targetValue - previousCommand);
21522
+ effectiveTarget = targetValue + commandDelta;
21523
+ }
21524
+ const delta = normalizeDelta(effectiveTarget - currentValue);
21258
21525
  const pathTarget = currentValue + delta;
21259
21526
  const requiresInterpolation = options.durationMs > 0 && Math.abs(delta) > NUMERIC_EPSILON;
21260
21527
  const state = {
@@ -21262,7 +21529,7 @@ const createNumericInterpolationState = (params) => {
21262
21529
  easing: options.easing,
21263
21530
  from: currentValue,
21264
21531
  to: pathTarget,
21265
- finalValue: targetValue,
21532
+ finalValue: effectiveTarget,
21266
21533
  startTimestamp: -1
21267
21534
  };
21268
21535
  return {
@@ -21270,7 +21537,7 @@ const createNumericInterpolationState = (params) => {
21270
21537
  requiresInterpolation
21271
21538
  };
21272
21539
  };
21273
- const clamp01 = (value) => {
21540
+ const clamp01$1 = (value) => {
21274
21541
  if (!Number.isFinite(value)) {
21275
21542
  return 1;
21276
21543
  }
@@ -21282,7 +21549,7 @@ const clamp01 = (value) => {
21282
21549
  }
21283
21550
  return value;
21284
21551
  };
21285
- const evaluateNumericInterpolation = (params) => {
21552
+ const evaluateDegreeInterpolation = (params) => {
21286
21553
  const { state } = params;
21287
21554
  const timestamp = Number.isFinite(params.timestamp) ? params.timestamp : Date.now();
21288
21555
  const duration = Math.max(0, state.durationMs);
@@ -21296,7 +21563,7 @@ const evaluateNumericInterpolation = (params) => {
21296
21563
  }
21297
21564
  const elapsed = timestamp - effectiveStart;
21298
21565
  const rawProgress = duration <= 0 ? 1 : elapsed / duration;
21299
- const eased = clamp01(state.easing(rawProgress));
21566
+ const eased = clamp01$1(state.easing(rawProgress));
21300
21567
  const interpolated = state.from + (state.to - state.from) * eased;
21301
21568
  const completed = rawProgress >= 1;
21302
21569
  return {
@@ -21305,7 +21572,7 @@ const evaluateNumericInterpolation = (params) => {
21305
21572
  effectiveStartTimestamp: effectiveStart
21306
21573
  };
21307
21574
  };
21308
- const normaliseAngleDeg = (angle) => {
21575
+ const normalizeAngleDeg = (angle) => {
21309
21576
  if (!Number.isFinite(angle)) {
21310
21577
  return 0;
21311
21578
  }
@@ -21315,17 +21582,19 @@ const normaliseAngleDeg = (angle) => {
21315
21582
  };
21316
21583
  const resolveRotationTarget = (params) => {
21317
21584
  const options = params.options;
21318
- const targetAngle = normaliseAngleDeg(params.targetAngleDeg);
21319
- const currentAngle = normaliseAngleDeg(params.currentAngleDeg);
21585
+ const targetAngle = normalizeAngleDeg(params.targetAngleDeg);
21586
+ const currentAngle = normalizeAngleDeg(params.currentAngleDeg);
21587
+ const previousCommandAngleDeg = params.previousCommandAngleDeg !== void 0 ? normalizeAngleDeg(params.previousCommandAngleDeg) : void 0;
21320
21588
  if (!options || options.durationMs <= 0) {
21321
21589
  return {
21322
21590
  nextAngleDeg: targetAngle,
21323
21591
  interpolationState: null
21324
21592
  };
21325
21593
  }
21326
- const { state, requiresInterpolation } = createNumericInterpolationState({
21594
+ const { state, requiresInterpolation } = createDegreeInterpolationState({
21327
21595
  currentValue: currentAngle,
21328
21596
  targetValue: targetAngle,
21597
+ previousCommandValue: previousCommandAngleDeg,
21329
21598
  options
21330
21599
  });
21331
21600
  if (!requiresInterpolation) {
@@ -21667,13 +21936,6 @@ const applySurfaceDisplacement = (baseLng, baseLat, east, north) => {
21667
21936
  lat: baseLat + deltaLat
21668
21937
  };
21669
21938
  };
21670
- const screenToClip = (x, y, drawingBufferWidth, drawingBufferHeight, pixelRatio) => {
21671
- const deviceX = x * pixelRatio;
21672
- const deviceY = y * pixelRatio;
21673
- const clipX = deviceX / drawingBufferWidth * 2 - 1;
21674
- const clipY = 1 - deviceY / drawingBufferHeight * 2;
21675
- return [clipX, clipY];
21676
- };
21677
21939
  const clipToScreen = (clipPosition, drawingBufferWidth, drawingBufferHeight, pixelRatio) => {
21678
21940
  const [clipX, clipY, , clipW] = clipPosition;
21679
21941
  if (!Number.isFinite(clipW) || clipW === 0) {
@@ -21818,41 +22080,55 @@ const calculateBillboardCenterPosition = (params) => {
21818
22080
  offsetShift
21819
22081
  };
21820
22082
  };
21821
- const BILLBOARD_BASE_CORNERS = [
21822
- [-1, 1],
21823
- [1, 1],
21824
- [-1, -1],
21825
- [1, -1]
21826
- ];
21827
- const calculateBillboardCornerScreenPositions = (params) => {
22083
+ const computeSurfaceCornerShaderModel = (params) => {
21828
22084
  var _a, _b;
21829
- const { centerX, centerY, halfWidth, halfHeight, anchor, totalRotateDeg } = params;
22085
+ const {
22086
+ baseLngLat,
22087
+ worldWidthMeters,
22088
+ worldHeightMeters,
22089
+ anchor,
22090
+ totalRotateDeg,
22091
+ offsetMeters
22092
+ } = params;
22093
+ const halfWidth = worldWidthMeters / 2;
22094
+ const halfHeight = worldHeightMeters / 2;
21830
22095
  if (halfWidth <= 0 || halfHeight <= 0) {
21831
- return UV_CORNERS.map(([u, v]) => ({ x: centerX, y: centerY, u, v }));
22096
+ const cosLat2 = Math.cos(baseLngLat.lat * DEG2RAD);
22097
+ const cosLatClamped2 = Math.max(cosLat2, MIN_COS_LAT);
22098
+ const deltaLat = offsetMeters.north / EARTH_RADIUS_METERS * RAD2DEG;
22099
+ const deltaLng = offsetMeters.east / (EARTH_RADIUS_METERS * cosLatClamped2) * RAD2DEG;
22100
+ return SURFACE_BASE_CORNERS$1.map(() => ({
22101
+ east: offsetMeters.east,
22102
+ north: offsetMeters.north,
22103
+ lng: baseLngLat.lng + deltaLng,
22104
+ lat: baseLngLat.lat + deltaLat
22105
+ }));
21832
22106
  }
21833
- const anchorOffsetX = ((_a = anchor == null ? void 0 : anchor.x) != null ? _a : 0) * halfWidth;
21834
- const anchorOffsetY = ((_b = anchor == null ? void 0 : anchor.y) != null ? _b : 0) * halfHeight;
22107
+ const anchorEast = ((_a = anchor == null ? void 0 : anchor.x) != null ? _a : 0) * halfWidth;
22108
+ const anchorNorth = ((_b = anchor == null ? void 0 : anchor.y) != null ? _b : 0) * halfHeight;
21835
22109
  const rad = -totalRotateDeg * DEG2RAD;
21836
- const cosR = Math.cos(rad);
21837
22110
  const sinR = Math.sin(rad);
21838
- const corners = [];
21839
- for (let i = 0; i < BILLBOARD_BASE_CORNERS.length; i++) {
21840
- const [cornerXNorm, cornerYNorm] = BILLBOARD_BASE_CORNERS[i];
21841
- const [u, v] = UV_CORNERS[i];
21842
- const cornerX = cornerXNorm * halfWidth;
21843
- const cornerY = cornerYNorm * halfHeight;
21844
- const shiftedX = cornerX - anchorOffsetX;
21845
- const shiftedY = cornerY - anchorOffsetY;
21846
- const rotatedX = shiftedX * cosR - shiftedY * sinR;
21847
- const rotatedY = shiftedX * sinR + shiftedY * cosR;
21848
- corners.push({
21849
- x: centerX + rotatedX,
21850
- y: centerY - rotatedY,
21851
- u,
21852
- v
21853
- });
21854
- }
21855
- return corners;
22111
+ const cosR = Math.cos(rad);
22112
+ const cosLat = Math.cos(baseLngLat.lat * DEG2RAD);
22113
+ const cosLatClamped = Math.max(cosLat, MIN_COS_LAT);
22114
+ return SURFACE_BASE_CORNERS$1.map(([eastNorm, northNorm]) => {
22115
+ const cornerEast = eastNorm * halfWidth;
22116
+ const cornerNorth = northNorm * halfHeight;
22117
+ const localEast = cornerEast - anchorEast;
22118
+ const localNorth = cornerNorth - anchorNorth;
22119
+ const rotatedEast = localEast * cosR - localNorth * sinR;
22120
+ const rotatedNorth = localEast * sinR + localNorth * cosR;
22121
+ const east = rotatedEast + offsetMeters.east;
22122
+ const north = rotatedNorth + offsetMeters.north;
22123
+ const deltaLat = north / EARTH_RADIUS_METERS * RAD2DEG;
22124
+ const deltaLng = east / (EARTH_RADIUS_METERS * cosLatClamped) * RAD2DEG;
22125
+ return {
22126
+ east,
22127
+ north,
22128
+ lng: baseLngLat.lng + deltaLng,
22129
+ lat: baseLngLat.lat + deltaLat
22130
+ };
22131
+ });
21856
22132
  };
21857
22133
  const calculateSurfaceCenterPosition = (params) => {
21858
22134
  var _a, _b;
@@ -21962,7 +22238,7 @@ const calculateSurfaceCenterPosition = (params) => {
21962
22238
  anchorlessLngLat
21963
22239
  };
21964
22240
  };
21965
- const SURFACE_BASE_CORNERS = [
22241
+ const SURFACE_BASE_CORNERS$1 = [
21966
22242
  [-1, 1],
21967
22243
  [1, 1],
21968
22244
  [-1, -1],
@@ -21978,7 +22254,7 @@ const calculateSurfaceCornerDisplacements = (params) => {
21978
22254
  offsetMeters
21979
22255
  } = params;
21980
22256
  if (worldWidthMeters <= 0 || worldHeightMeters <= 0) {
21981
- return SURFACE_BASE_CORNERS.map(() => ({
22257
+ return SURFACE_BASE_CORNERS$1.map(() => ({
21982
22258
  east: offsetMeters.east,
21983
22259
  north: offsetMeters.north
21984
22260
  }));
@@ -21991,7 +22267,7 @@ const calculateSurfaceCornerDisplacements = (params) => {
21991
22267
  const cosR = Math.cos(rad);
21992
22268
  const sinR = Math.sin(rad);
21993
22269
  const corners = [];
21994
- for (const [eastNorm, northNorm] of SURFACE_BASE_CORNERS) {
22270
+ for (const [eastNorm, northNorm] of SURFACE_BASE_CORNERS$1) {
21995
22271
  const cornerEast = eastNorm * halfWidth;
21996
22272
  const cornerNorth = northNorm * halfHeight;
21997
22273
  const localEast = cornerEast - anchorEast;
@@ -22005,115 +22281,602 @@ const calculateSurfaceCornerDisplacements = (params) => {
22005
22281
  }
22006
22282
  return corners;
22007
22283
  };
22008
- const DEFAULT_ANCHOR = { x: 0, y: 0 };
22009
- const DEFAULT_AUTO_ROTATION_MIN_DISTANCE_METERS = 20;
22010
- const DEFAULT_IMAGE_OFFSET = {
22011
- offsetMeters: 0,
22012
- offsetDeg: 0
22013
- };
22014
- const MIN_CLIP_W = 1e-6;
22015
- const MIN_CLIP_Z_EPSILON = 1e-7;
22016
- const EPS_NDC = 1e-6;
22017
- const ORDER_MAX = 16;
22018
- const ORDER_BUCKET = 16;
22019
- const MIN_FILTER_VALUES = [
22020
- "nearest",
22021
- "linear",
22022
- "nearest-mipmap-nearest",
22023
- "nearest-mipmap-linear",
22024
- "linear-mipmap-nearest",
22025
- "linear-mipmap-linear"
22026
- ];
22027
- const MAG_FILTER_VALUES = [
22028
- "nearest",
22029
- "linear"
22030
- ];
22031
- const MIPMAP_MIN_FILTERS = /* @__PURE__ */ new Set([
22032
- "nearest-mipmap-nearest",
22033
- "nearest-mipmap-linear",
22034
- "linear-mipmap-nearest",
22035
- "linear-mipmap-linear"
22036
- ]);
22037
- const filterRequiresMipmaps = (filter) => MIPMAP_MIN_FILTERS.has(filter);
22038
- const resolveTextureFilteringOptions = (options) => {
22039
- var _a, _b;
22040
- const minCandidate = options == null ? void 0 : options.minFilter;
22041
- const minFilter = MIN_FILTER_VALUES.includes(
22042
- minCandidate
22043
- ) ? minCandidate : DEFAULT_TEXTURE_FILTERING_OPTIONS.minFilter;
22044
- const magCandidate = options == null ? void 0 : options.magFilter;
22045
- const magFilter = MAG_FILTER_VALUES.includes(
22046
- magCandidate
22047
- ) ? magCandidate : DEFAULT_TEXTURE_FILTERING_OPTIONS.magFilter;
22048
- let generateMipmaps = (_a = options == null ? void 0 : options.generateMipmaps) != null ? _a : DEFAULT_TEXTURE_FILTERING_OPTIONS.generateMipmaps;
22049
- if (filterRequiresMipmaps(minFilter)) {
22050
- generateMipmaps = true;
22051
- }
22052
- let maxAnisotropy = (_b = options == null ? void 0 : options.maxAnisotropy) != null ? _b : DEFAULT_TEXTURE_FILTERING_OPTIONS.maxAnisotropy;
22053
- if (!Number.isFinite(maxAnisotropy) || maxAnisotropy < 1) {
22054
- maxAnisotropy = 1;
22055
- }
22284
+ const DISTANCE_EPSILON = 1e-6;
22285
+ const normalizeDuration = (durationMs) => Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0;
22286
+ const normalizeOptions = (options) => {
22287
+ var _a;
22056
22288
  return {
22057
- minFilter,
22058
- magFilter,
22059
- generateMipmaps,
22060
- maxAnisotropy
22289
+ durationMs: normalizeDuration(options.durationMs),
22290
+ easing: resolveEasing(options.easing),
22291
+ mode: (_a = options.mode) != null ? _a : "feedback"
22061
22292
  };
22062
22293
  };
22063
- const ANISOTROPY_EXTENSION_NAMES = [
22064
- "EXT_texture_filter_anisotropic",
22065
- "WEBKIT_EXT_texture_filter_anisotropic",
22066
- "MOZ_EXT_texture_filter_anisotropic"
22067
- ];
22068
- const resolveAnisotropyExtension = (glContext) => {
22069
- for (const name of ANISOTROPY_EXTENSION_NAMES) {
22070
- const extension = glContext.getExtension(name);
22071
- if (extension) {
22072
- return extension;
22073
- }
22294
+ const createDistanceInterpolationState = (params) => {
22295
+ const { currentValue, targetValue } = params;
22296
+ const options = normalizeOptions(params.options);
22297
+ let effectiveTarget = targetValue;
22298
+ const previousCommand = params.previousCommandValue;
22299
+ if (options.mode === "feedforward" && previousCommand !== void 0 && Number.isFinite(previousCommand)) {
22300
+ const commandDelta = targetValue - previousCommand;
22301
+ effectiveTarget = targetValue + commandDelta;
22074
22302
  }
22075
- return null;
22303
+ const delta = effectiveTarget - currentValue;
22304
+ const requiresInterpolation = options.durationMs > 0 && Math.abs(delta) > DISTANCE_EPSILON;
22305
+ const state = {
22306
+ durationMs: options.durationMs,
22307
+ easing: options.easing,
22308
+ from: currentValue,
22309
+ to: currentValue + delta,
22310
+ finalValue: effectiveTarget,
22311
+ startTimestamp: -1
22312
+ };
22313
+ return {
22314
+ state,
22315
+ requiresInterpolation
22316
+ };
22076
22317
  };
22077
- const isPowerOfTwo = (value) => value > 0 && (value & value - 1) === 0;
22078
- const resolveGlMinFilter = (glContext, filter) => {
22079
- switch (filter) {
22080
- case "nearest":
22081
- return glContext.NEAREST;
22082
- case "nearest-mipmap-nearest":
22083
- return glContext.NEAREST_MIPMAP_NEAREST;
22084
- case "nearest-mipmap-linear":
22085
- return glContext.NEAREST_MIPMAP_LINEAR;
22086
- case "linear-mipmap-nearest":
22087
- return glContext.LINEAR_MIPMAP_NEAREST;
22088
- case "linear-mipmap-linear":
22089
- return glContext.LINEAR_MIPMAP_LINEAR;
22090
- case "linear":
22091
- default:
22092
- return glContext.LINEAR;
22318
+ const clamp01 = (value) => {
22319
+ if (!Number.isFinite(value)) {
22320
+ return 1;
22093
22321
  }
22094
- };
22095
- const resolveGlMagFilter = (glContext, filter) => {
22096
- switch (filter) {
22097
- case "nearest":
22098
- return glContext.NEAREST;
22099
- case "linear":
22100
- default:
22101
- return glContext.LINEAR;
22322
+ if (value <= 0) {
22323
+ return 0;
22102
22324
  }
22103
- };
22104
- const calculatePerspectiveRatio = (mapInstance, location2) => {
22105
- var _a, _b, _c;
22106
- const transform = mapInstance.transform;
22107
- if (!transform) {
22325
+ if (value >= 1) {
22108
22326
  return 1;
22109
22327
  }
22110
- const mercatorMatrix = (_a = transform.mercatorMatrix) != null ? _a : transform._mercatorMatrix;
22328
+ return value;
22329
+ };
22330
+ const evaluateDistanceInterpolation = (params) => {
22331
+ const { state } = params;
22332
+ const timestamp = Number.isFinite(params.timestamp) ? params.timestamp : Date.now();
22333
+ const duration = Math.max(0, state.durationMs);
22334
+ const effectiveStart = state.startTimestamp >= 0 ? state.startTimestamp : timestamp;
22335
+ if (duration === 0 || Math.abs(state.to - state.from) <= DISTANCE_EPSILON) {
22336
+ return {
22337
+ value: state.finalValue,
22338
+ completed: true,
22339
+ effectiveStartTimestamp: effectiveStart
22340
+ };
22341
+ }
22342
+ const elapsed = timestamp - effectiveStart;
22343
+ const rawProgress = duration <= 0 ? 1 : elapsed / duration;
22344
+ const eased = clamp01(state.easing(rawProgress));
22345
+ const interpolated = state.from + (state.to - state.from) * eased;
22346
+ const completed = rawProgress >= 1;
22347
+ return {
22348
+ value: completed ? state.finalValue : interpolated,
22349
+ completed,
22350
+ effectiveStartTimestamp: effectiveStart
22351
+ };
22352
+ };
22353
+ const stepDegreeInterpolationState = (interpolationState, timestamp, applyValue, options) => {
22354
+ var _a, _b;
22355
+ if (!interpolationState) {
22356
+ return { state: null, active: false };
22357
+ }
22358
+ const evaluation = evaluateDegreeInterpolation({
22359
+ state: interpolationState,
22360
+ timestamp
22361
+ });
22362
+ if (interpolationState.startTimestamp < 0) {
22363
+ interpolationState.startTimestamp = evaluation.effectiveStartTimestamp;
22364
+ }
22365
+ const normalizeValue = (_a = options == null ? void 0 : options.normalize) != null ? _a : ((value) => value);
22366
+ const applyFinalValue = (_b = options == null ? void 0 : options.applyFinalValue) != null ? _b : applyValue;
22367
+ const interpolatedValue = normalizeValue(evaluation.value);
22368
+ applyValue(interpolatedValue);
22369
+ if (evaluation.completed) {
22370
+ const finalValue = normalizeValue(interpolationState.finalValue);
22371
+ applyFinalValue(finalValue);
22372
+ return { state: null, active: false };
22373
+ }
22374
+ return { state: interpolationState, active: true };
22375
+ };
22376
+ const updateImageDisplayedRotation = (image, optionsOverride) => {
22377
+ const targetAngle = normalizeAngleDeg(
22378
+ image.resolvedBaseRotateDeg + image.rotateDeg
22379
+ );
22380
+ const currentAngle = Number.isFinite(image.displayedRotateDeg) ? image.displayedRotateDeg : targetAngle;
22381
+ const previousCommandAngle = image.lastCommandRotateDeg;
22382
+ const options = optionsOverride === void 0 ? image.rotationInterpolationOptions : optionsOverride;
22383
+ const { nextAngleDeg, interpolationState } = resolveRotationTarget({
22384
+ currentAngleDeg: currentAngle,
22385
+ targetAngleDeg: targetAngle,
22386
+ previousCommandAngleDeg: previousCommandAngle,
22387
+ options: options != null ? options : void 0
22388
+ });
22389
+ image.displayedRotateDeg = nextAngleDeg;
22390
+ image.rotationInterpolationState = interpolationState;
22391
+ if (!interpolationState) {
22392
+ image.displayedRotateDeg = targetAngle;
22393
+ }
22394
+ image.lastCommandRotateDeg = targetAngle;
22395
+ };
22396
+ const syncImageRotationChannel = (image, optionsOverride) => {
22397
+ updateImageDisplayedRotation(image, optionsOverride);
22398
+ };
22399
+ const stepRotationInterpolation = (image, timestamp) => {
22400
+ const { state, active } = stepDegreeInterpolationState(
22401
+ image.rotationInterpolationState,
22402
+ timestamp,
22403
+ (value) => {
22404
+ image.displayedRotateDeg = value;
22405
+ },
22406
+ {
22407
+ normalize: normalizeAngleDeg
22408
+ }
22409
+ );
22410
+ image.rotationInterpolationState = state;
22411
+ return active;
22412
+ };
22413
+ const stepOffsetDegInterpolation = (image, timestamp) => {
22414
+ const { state, active } = stepDegreeInterpolationState(
22415
+ image.offsetDegInterpolationState,
22416
+ timestamp,
22417
+ (value) => {
22418
+ image.offset.offsetDeg = value;
22419
+ }
22420
+ );
22421
+ image.offsetDegInterpolationState = state;
22422
+ return active;
22423
+ };
22424
+ const clearOffsetDegInterpolation = (image) => {
22425
+ image.offsetDegInterpolationState = null;
22426
+ };
22427
+ const stepDistanceInterpolationState = (interpolationState, timestamp, applyValue) => {
22428
+ if (!interpolationState) {
22429
+ return { state: null, active: false };
22430
+ }
22431
+ const evaluation = evaluateDistanceInterpolation({
22432
+ state: interpolationState,
22433
+ timestamp
22434
+ });
22435
+ if (interpolationState.startTimestamp < 0) {
22436
+ interpolationState.startTimestamp = evaluation.effectiveStartTimestamp;
22437
+ }
22438
+ applyValue(evaluation.value);
22439
+ if (evaluation.completed) {
22440
+ applyValue(interpolationState.finalValue);
22441
+ return { state: null, active: false };
22442
+ }
22443
+ return { state: interpolationState, active: true };
22444
+ };
22445
+ const clearOffsetMetersInterpolation = (image) => {
22446
+ image.offsetMetersInterpolationState = null;
22447
+ };
22448
+ const applyOffsetDegUpdate = (image, nextOffset, interpolationOptions) => {
22449
+ const options = interpolationOptions;
22450
+ if (!options || options.durationMs <= 0) {
22451
+ image.offset.offsetDeg = nextOffset.offsetDeg;
22452
+ image.offsetDegInterpolationState = null;
22453
+ image.lastCommandOffsetDeg = nextOffset.offsetDeg;
22454
+ return;
22455
+ }
22456
+ const { state, requiresInterpolation } = createDegreeInterpolationState({
22457
+ currentValue: image.offset.offsetDeg,
22458
+ targetValue: nextOffset.offsetDeg,
22459
+ previousCommandValue: image.lastCommandOffsetDeg,
22460
+ options
22461
+ });
22462
+ image.lastCommandOffsetDeg = nextOffset.offsetDeg;
22463
+ if (requiresInterpolation) {
22464
+ image.offsetDegInterpolationState = state;
22465
+ } else {
22466
+ image.offset.offsetDeg = nextOffset.offsetDeg;
22467
+ image.offsetDegInterpolationState = null;
22468
+ }
22469
+ };
22470
+ const applyOffsetMetersUpdate = (image, nextOffset, interpolationOptions) => {
22471
+ const options = interpolationOptions;
22472
+ if (!options || options.durationMs <= 0) {
22473
+ image.offset.offsetMeters = nextOffset.offsetMeters;
22474
+ image.offsetMetersInterpolationState = null;
22475
+ image.lastCommandOffsetMeters = nextOffset.offsetMeters;
22476
+ return;
22477
+ }
22478
+ const { state, requiresInterpolation } = createDistanceInterpolationState({
22479
+ currentValue: image.offset.offsetMeters,
22480
+ targetValue: nextOffset.offsetMeters,
22481
+ previousCommandValue: image.lastCommandOffsetMeters,
22482
+ options
22483
+ });
22484
+ image.lastCommandOffsetMeters = nextOffset.offsetMeters;
22485
+ if (requiresInterpolation) {
22486
+ image.offsetMetersInterpolationState = state;
22487
+ } else {
22488
+ image.offset.offsetMeters = nextOffset.offsetMeters;
22489
+ image.offsetMetersInterpolationState = null;
22490
+ }
22491
+ };
22492
+ const stepOffsetMetersInterpolation = (image, timestamp) => {
22493
+ const { state, active } = stepDistanceInterpolationState(
22494
+ image.offsetMetersInterpolationState,
22495
+ timestamp,
22496
+ (value) => {
22497
+ image.offset.offsetMeters = value;
22498
+ }
22499
+ );
22500
+ image.offsetMetersInterpolationState = state;
22501
+ return active;
22502
+ };
22503
+ const IMAGE_INTERPOLATION_STEPPERS = [
22504
+ stepRotationInterpolation,
22505
+ stepOffsetDegInterpolation,
22506
+ stepOffsetMetersInterpolation
22507
+ ];
22508
+ const stepSpriteImageInterpolations = (image, timestamp) => {
22509
+ let active = false;
22510
+ for (const stepper of IMAGE_INTERPOLATION_STEPPERS) {
22511
+ if (stepper(image, timestamp)) {
22512
+ active = true;
22513
+ }
22514
+ }
22515
+ return active;
22516
+ };
22517
+ const applyOffsetUpdate = (image, nextOffset, options = {}) => {
22518
+ applyOffsetDegUpdate(image, nextOffset, options.deg);
22519
+ applyOffsetMetersUpdate(image, nextOffset, options.meters);
22520
+ };
22521
+ const DEFAULT_MAX_ITEMS_PER_NODE = 16;
22522
+ const DEFAULT_MAX_DEPTH = 8;
22523
+ const DEFAULT_LOOSENESS = 1.5;
22524
+ const createNode = (bounds, looseness, depth) => ({
22525
+ bounds,
22526
+ looseBounds: expandRect(bounds, looseness),
22527
+ items: [],
22528
+ children: null,
22529
+ depth
22530
+ });
22531
+ const normalizeRect = (rect) => {
22532
+ const x0 = Math.min(rect.x0, rect.x1);
22533
+ const y0 = Math.min(rect.y0, rect.y1);
22534
+ const x1 = Math.max(rect.x0, rect.x1);
22535
+ const y1 = Math.max(rect.y0, rect.y1);
22536
+ return { x0, y0, x1, y1 };
22537
+ };
22538
+ const isFiniteRect = (rect) => Number.isFinite(rect.x0) && Number.isFinite(rect.y0) && Number.isFinite(rect.x1) && Number.isFinite(rect.y1);
22539
+ const expandRect = (rect, looseness) => {
22540
+ if (looseness === 1) {
22541
+ return rect;
22542
+ }
22543
+ const width = rect.x1 - rect.x0;
22544
+ const height = rect.y1 - rect.y0;
22545
+ const halfWidth = width * looseness / 2;
22546
+ const halfHeight = height * looseness / 2;
22547
+ const centerX = rect.x0 + width / 2;
22548
+ const centerY = rect.y0 + height / 2;
22549
+ return {
22550
+ x0: centerX - halfWidth,
22551
+ y0: centerY - halfHeight,
22552
+ x1: centerX + halfWidth,
22553
+ y1: centerY + halfHeight
22554
+ };
22555
+ };
22556
+ const rectContainsRectInclusive = (container, target) => container.x0 <= target.x0 && container.y0 <= target.y0 && container.x1 >= target.x1 && container.y1 >= target.y1;
22557
+ const rectsOverlapInclusive = (a, b) => !(a.x1 < b.x0 || a.x0 > b.x1 || a.y1 < b.y0 || a.y0 > b.y1);
22558
+ const rectEquals = (a, b) => a.x0 === b.x0 && a.y0 === b.y0 && a.x1 === b.x1 && a.y1 === b.y1;
22559
+ const createLooseQuadTree = (options) => {
22560
+ var _a, _b, _c;
22561
+ const maxItemsPerNode = (_a = options.maxItemsPerNode) != null ? _a : DEFAULT_MAX_ITEMS_PER_NODE;
22562
+ const maxDepth = (_b = options.maxDepth) != null ? _b : DEFAULT_MAX_DEPTH;
22563
+ const looseness = (_c = options.looseness) != null ? _c : DEFAULT_LOOSENESS;
22564
+ if (maxItemsPerNode <= 0) {
22565
+ throw new Error("maxItemsPerNode must be greater than 0.");
22566
+ }
22567
+ if (maxDepth < 0) {
22568
+ throw new Error("maxDepth must be 0 or greater.");
22569
+ }
22570
+ if (!(looseness >= 1)) {
22571
+ throw new Error("looseness must be greater than or equal to 1.");
22572
+ }
22573
+ const normalizedBounds = normalizeRect(options.bounds);
22574
+ if (!isFiniteRect(normalizedBounds)) {
22575
+ throw new Error("Bounds must have finite coordinates.");
22576
+ }
22577
+ let root = createNode(normalizedBounds, looseness, 0);
22578
+ let count = 0;
22579
+ let registry = /* @__PURE__ */ new WeakMap();
22580
+ const insertIntoNode = (node, quadItem) => {
22581
+ if (node.children) {
22582
+ const childIndex = findChildIndex(node.children, quadItem.rect);
22583
+ if (childIndex !== -1) {
22584
+ insertIntoNode(node.children[childIndex], quadItem);
22585
+ return;
22586
+ }
22587
+ }
22588
+ quadItem.node = node;
22589
+ quadItem.index = node.items.length;
22590
+ node.items.push(quadItem);
22591
+ if (node.items.length > maxItemsPerNode && node.depth < maxDepth) {
22592
+ if (!node.children) {
22593
+ subdivide(node);
22594
+ }
22595
+ const children = node.children;
22596
+ if (!children) {
22597
+ return;
22598
+ }
22599
+ let i = 0;
22600
+ while (i < node.items.length) {
22601
+ const candidate = node.items[i];
22602
+ const childIndex = findChildIndex(children, candidate.rect);
22603
+ if (childIndex !== -1) {
22604
+ detachFromNode(candidate);
22605
+ insertIntoNode(children[childIndex], candidate);
22606
+ } else {
22607
+ i += 1;
22608
+ }
22609
+ }
22610
+ }
22611
+ };
22612
+ const removeQuadItem = (quadItem) => {
22613
+ detachFromNode(quadItem);
22614
+ registry.delete(quadItem.item);
22615
+ };
22616
+ const detachFromNode = (quadItem) => {
22617
+ const node = quadItem.node;
22618
+ const index = quadItem.index;
22619
+ if (index < 0 || index >= node.items.length) {
22620
+ return;
22621
+ }
22622
+ const lastIndex = node.items.length - 1;
22623
+ if (index !== lastIndex) {
22624
+ const moved = node.items[lastIndex];
22625
+ node.items[index] = moved;
22626
+ moved.index = index;
22627
+ }
22628
+ node.items.pop();
22629
+ quadItem.index = -1;
22630
+ quadItem.node = node;
22631
+ };
22632
+ const reassignIfPossible = (quadItem) => {
22633
+ const node = quadItem.node;
22634
+ if (!node.children) {
22635
+ return;
22636
+ }
22637
+ const childIndex = findChildIndex(node.children, quadItem.rect);
22638
+ if (childIndex === -1) {
22639
+ return;
22640
+ }
22641
+ detachFromNode(quadItem);
22642
+ insertIntoNode(node.children[childIndex], quadItem);
22643
+ };
22644
+ const add = (item) => {
22645
+ const rect = normalizeRect(item);
22646
+ if (!rectContainsRectInclusive(root.bounds, rect)) {
22647
+ throw new Error("Item rectangle is outside of quadtree bounds.");
22648
+ }
22649
+ const quadItem = {
22650
+ item,
22651
+ rect,
22652
+ node: root,
22653
+ index: -1
22654
+ };
22655
+ insertIntoNode(root, quadItem);
22656
+ registry.set(item, quadItem);
22657
+ count += 1;
22658
+ };
22659
+ const remove = (x0, y0, x1, y1, item) => {
22660
+ const quadItem = registry.get(item);
22661
+ if (!quadItem) {
22662
+ return false;
22663
+ }
22664
+ const rect = normalizeRect({ x0, y0, x1, y1 });
22665
+ if (!rectEquals(rect, quadItem.rect)) {
22666
+ return false;
22667
+ }
22668
+ removeQuadItem(quadItem);
22669
+ count -= 1;
22670
+ return true;
22671
+ };
22672
+ const update = (oldX0, oldY0, oldX1, oldY1, newX0, newY0, newX1, newY1, item) => {
22673
+ const quadItem = registry.get(item);
22674
+ if (!quadItem) {
22675
+ return false;
22676
+ }
22677
+ const currentRect = quadItem.rect;
22678
+ const expectedOldRect = normalizeRect({
22679
+ x0: oldX0,
22680
+ y0: oldY0,
22681
+ x1: oldX1,
22682
+ y1: oldY1
22683
+ });
22684
+ if (!rectEquals(currentRect, expectedOldRect)) {
22685
+ return false;
22686
+ }
22687
+ const newRect = normalizeRect({
22688
+ x0: newX0,
22689
+ y0: newY0,
22690
+ x1: newX1,
22691
+ y1: newY1
22692
+ });
22693
+ if (!rectContainsRectInclusive(root.bounds, newRect)) {
22694
+ throw new Error("Updated rectangle is outside of quadtree bounds.");
22695
+ }
22696
+ if (rectContainsRectInclusive(quadItem.node.looseBounds, newRect)) {
22697
+ quadItem.rect = newRect;
22698
+ reassignIfPossible(quadItem);
22699
+ return true;
22700
+ }
22701
+ detachFromNode(quadItem);
22702
+ quadItem.rect = newRect;
22703
+ insertIntoNode(root, quadItem);
22704
+ return true;
22705
+ };
22706
+ const lookup = (x0, y0, x1, y1) => {
22707
+ const rect = normalizeRect({ x0, y0, x1, y1 });
22708
+ const results = [];
22709
+ collectFromNode(root, rect, results);
22710
+ return results;
22711
+ };
22712
+ const clear = () => {
22713
+ root = createNode(normalizedBounds, looseness, 0);
22714
+ registry = /* @__PURE__ */ new WeakMap();
22715
+ count = 0;
22716
+ };
22717
+ const collectFromNode = (node, rect, results) => {
22718
+ if (!rectsOverlapInclusive(node.looseBounds, rect)) {
22719
+ return;
22720
+ }
22721
+ for (const quadItem of node.items) {
22722
+ if (rectsOverlapInclusive(quadItem.rect, rect)) {
22723
+ results.push(quadItem.item);
22724
+ }
22725
+ }
22726
+ if (!node.children) {
22727
+ return;
22728
+ }
22729
+ for (const child of node.children) {
22730
+ collectFromNode(child, rect, results);
22731
+ }
22732
+ };
22733
+ const findChildIndex = (children, rect) => {
22734
+ for (let i = 0; i < children.length; i += 1) {
22735
+ const child = children[i];
22736
+ if (rectContainsRectInclusive(child.looseBounds, rect)) {
22737
+ return i;
22738
+ }
22739
+ }
22740
+ return -1;
22741
+ };
22742
+ const subdivide = (node) => {
22743
+ if (node.children) {
22744
+ return;
22745
+ }
22746
+ const { bounds } = node;
22747
+ const splitX = bounds.x0 + (bounds.x1 - bounds.x0) / 2;
22748
+ const splitY = bounds.y0 + (bounds.y1 - bounds.y0) / 2;
22749
+ const childrenBounds = [
22750
+ normalizeRect({ x0: bounds.x0, y0: bounds.y0, x1: splitX, y1: splitY }),
22751
+ normalizeRect({ x0: splitX, y0: bounds.y0, x1: bounds.x1, y1: splitY }),
22752
+ normalizeRect({ x0: bounds.x0, y0: splitY, x1: splitX, y1: bounds.y1 }),
22753
+ normalizeRect({ x0: splitX, y0: splitY, x1: bounds.x1, y1: bounds.y1 })
22754
+ ];
22755
+ node.children = childrenBounds.map(
22756
+ (childBounds) => createNode(childBounds, looseness, node.depth + 1)
22757
+ );
22758
+ };
22759
+ return {
22760
+ get size() {
22761
+ return count;
22762
+ },
22763
+ add,
22764
+ remove,
22765
+ update,
22766
+ lookup,
22767
+ clear
22768
+ };
22769
+ };
22770
+ const DEFAULT_ANCHOR = { x: 0, y: 0 };
22771
+ const DEFAULT_AUTO_ROTATION_MIN_DISTANCE_METERS = 20;
22772
+ const DEFAULT_IMAGE_OFFSET = {
22773
+ offsetMeters: 0,
22774
+ offsetDeg: 0
22775
+ };
22776
+ const HIT_TEST_QUERY_RADIUS_PIXELS = 32;
22777
+ const MIN_CLIP_W = 1e-6;
22778
+ const MIN_CLIP_Z_EPSILON = 1e-7;
22779
+ const EPS_NDC = 1e-6;
22780
+ const ORDER_MAX = 16;
22781
+ const ORDER_BUCKET = 16;
22782
+ const MIN_FILTER_VALUES = [
22783
+ "nearest",
22784
+ "linear",
22785
+ "nearest-mipmap-nearest",
22786
+ "nearest-mipmap-linear",
22787
+ "linear-mipmap-nearest",
22788
+ "linear-mipmap-linear"
22789
+ ];
22790
+ const MAG_FILTER_VALUES = [
22791
+ "nearest",
22792
+ "linear"
22793
+ ];
22794
+ const MIPMAP_MIN_FILTERS = /* @__PURE__ */ new Set([
22795
+ "nearest-mipmap-nearest",
22796
+ "nearest-mipmap-linear",
22797
+ "linear-mipmap-nearest",
22798
+ "linear-mipmap-linear"
22799
+ ]);
22800
+ const filterRequiresMipmaps = (filter) => MIPMAP_MIN_FILTERS.has(filter);
22801
+ const resolveTextureFilteringOptions = (options) => {
22802
+ var _a, _b;
22803
+ const minCandidate = options == null ? void 0 : options.minFilter;
22804
+ const minFilter = MIN_FILTER_VALUES.includes(
22805
+ minCandidate
22806
+ ) ? minCandidate : DEFAULT_TEXTURE_FILTERING_OPTIONS.minFilter;
22807
+ const magCandidate = options == null ? void 0 : options.magFilter;
22808
+ const magFilter = MAG_FILTER_VALUES.includes(
22809
+ magCandidate
22810
+ ) ? magCandidate : DEFAULT_TEXTURE_FILTERING_OPTIONS.magFilter;
22811
+ let generateMipmaps = (_a = options == null ? void 0 : options.generateMipmaps) != null ? _a : DEFAULT_TEXTURE_FILTERING_OPTIONS.generateMipmaps;
22812
+ if (filterRequiresMipmaps(minFilter)) {
22813
+ generateMipmaps = true;
22814
+ }
22815
+ let maxAnisotropy = (_b = options == null ? void 0 : options.maxAnisotropy) != null ? _b : DEFAULT_TEXTURE_FILTERING_OPTIONS.maxAnisotropy;
22816
+ if (!Number.isFinite(maxAnisotropy) || maxAnisotropy < 1) {
22817
+ maxAnisotropy = 1;
22818
+ }
22819
+ return {
22820
+ minFilter,
22821
+ magFilter,
22822
+ generateMipmaps,
22823
+ maxAnisotropy
22824
+ };
22825
+ };
22826
+ const ANISOTROPY_EXTENSION_NAMES = [
22827
+ "EXT_texture_filter_anisotropic",
22828
+ "WEBKIT_EXT_texture_filter_anisotropic",
22829
+ "MOZ_EXT_texture_filter_anisotropic"
22830
+ ];
22831
+ const resolveAnisotropyExtension = (glContext) => {
22832
+ for (const name of ANISOTROPY_EXTENSION_NAMES) {
22833
+ const extension = glContext.getExtension(name);
22834
+ if (extension) {
22835
+ return extension;
22836
+ }
22837
+ }
22838
+ return null;
22839
+ };
22840
+ const isPowerOfTwo = (value) => value > 0 && (value & value - 1) === 0;
22841
+ const resolveGlMinFilter = (glContext, filter) => {
22842
+ switch (filter) {
22843
+ case "nearest":
22844
+ return glContext.NEAREST;
22845
+ case "nearest-mipmap-nearest":
22846
+ return glContext.NEAREST_MIPMAP_NEAREST;
22847
+ case "nearest-mipmap-linear":
22848
+ return glContext.NEAREST_MIPMAP_LINEAR;
22849
+ case "linear-mipmap-nearest":
22850
+ return glContext.LINEAR_MIPMAP_NEAREST;
22851
+ case "linear-mipmap-linear":
22852
+ return glContext.LINEAR_MIPMAP_LINEAR;
22853
+ case "linear":
22854
+ default:
22855
+ return glContext.LINEAR;
22856
+ }
22857
+ };
22858
+ const resolveGlMagFilter = (glContext, filter) => {
22859
+ switch (filter) {
22860
+ case "nearest":
22861
+ return glContext.NEAREST;
22862
+ case "linear":
22863
+ default:
22864
+ return glContext.LINEAR;
22865
+ }
22866
+ };
22867
+ const calculatePerspectiveRatio = (mapInstance, location2, cachedMercator) => {
22868
+ var _a, _b, _c;
22869
+ const transform = mapInstance.transform;
22870
+ if (!transform) {
22871
+ return 1;
22872
+ }
22873
+ const mercatorMatrix = (_a = transform.mercatorMatrix) != null ? _a : transform._mercatorMatrix;
22111
22874
  const cameraToCenterDistance = transform.cameraToCenterDistance;
22112
22875
  if (!mercatorMatrix || typeof cameraToCenterDistance !== "number" || !Number.isFinite(cameraToCenterDistance)) {
22113
22876
  return 1;
22114
22877
  }
22115
22878
  try {
22116
- const mercator = maplibreGlExports.MercatorCoordinate.fromLngLat(
22879
+ const mercator = cachedMercator != null ? cachedMercator : maplibreGlExports.MercatorCoordinate.fromLngLat(
22117
22880
  { lng: location2.lng, lat: location2.lat },
22118
22881
  (_b = location2.z) != null ? _b : 0
22119
22882
  );
@@ -22222,14 +22985,14 @@ const applyAutoRotation = (sprite, nextLocation) => {
22222
22985
  return false;
22223
22986
  }
22224
22987
  const resolvedAngleRaw = isFiniteNumber(bearingDeg) ? bearingDeg : sprite.lastAutoRotationAngleDeg;
22225
- const resolvedAngle = normaliseAngleDeg(resolvedAngleRaw);
22988
+ const resolvedAngle = normalizeAngleDeg(resolvedAngleRaw);
22226
22989
  sprite.images.forEach((orderMap) => {
22227
22990
  orderMap.forEach((image) => {
22228
22991
  if (!image.autoRotation) {
22229
22992
  return;
22230
22993
  }
22231
22994
  image.resolvedBaseRotateDeg = resolvedAngle;
22232
- updateImageDisplayedRotation(image);
22995
+ syncImageRotationChannel(image);
22233
22996
  });
22234
22997
  });
22235
22998
  sprite.lastAutoRotationLocation = cloneSpriteLocation(nextLocation);
@@ -22246,10 +23009,59 @@ const QUAD_VERTEX_COUNT = 6;
22246
23009
  const VERTEX_SHADER_SOURCE = `
22247
23010
  attribute vec4 a_position;
22248
23011
  attribute vec2 a_uv;
23012
+ uniform vec2 u_screenToClipScale;
23013
+ uniform vec2 u_screenToClipOffset;
23014
+ uniform float u_billboardMode;
23015
+ uniform float u_surfaceMode;
23016
+ uniform vec2 u_billboardCenter;
23017
+ uniform vec2 u_billboardHalfSize;
23018
+ uniform vec2 u_billboardAnchor;
23019
+ uniform vec2 u_billboardSinCos;
23020
+ uniform float u_surfaceClipEnabled;
23021
+ uniform vec4 u_surfaceClipCenter;
23022
+ uniform vec4 u_surfaceClipBasisEast;
23023
+ uniform vec4 u_surfaceClipBasisNorth;
23024
+ uniform float u_surfaceDepthBias;
22249
23025
  varying vec2 v_uv;
23026
+ vec2 computeBillboardCorner(vec2 uv) {
23027
+ vec2 base = vec2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
23028
+ vec2 anchorShift = vec2(u_billboardAnchor.x * u_billboardHalfSize.x, u_billboardAnchor.y * u_billboardHalfSize.y);
23029
+ vec2 shifted = vec2(base.x * u_billboardHalfSize.x, base.y * u_billboardHalfSize.y) - anchorShift;
23030
+ float sinR = u_billboardSinCos.x;
23031
+ float cosR = u_billboardSinCos.y;
23032
+ vec2 rotated = vec2(
23033
+ shifted.x * cosR - shifted.y * sinR,
23034
+ shifted.x * sinR + shifted.y * cosR
23035
+ );
23036
+ return vec2(
23037
+ u_billboardCenter.x + rotated.x,
23038
+ u_billboardCenter.y - rotated.y
23039
+ );
23040
+ }
23041
+ vec4 computeSurfaceCorner(vec2 corner) {
23042
+ if (u_surfaceClipEnabled < 0.5) {
23043
+ return vec4(0.0, 0.0, 0.0, 1.0);
23044
+ }
23045
+ vec4 clip = u_surfaceClipCenter
23046
+ + (corner.x * u_surfaceClipBasisEast)
23047
+ + (corner.y * u_surfaceClipBasisNorth);
23048
+ clip.z += u_surfaceDepthBias * clip.w;
23049
+ return clip;
23050
+ }
22250
23051
  void main() {
22251
23052
  v_uv = a_uv;
22252
- gl_Position = a_position;
23053
+ vec4 position;
23054
+ if (u_billboardMode > 0.5) {
23055
+ vec2 screenPosition = computeBillboardCorner(a_uv);
23056
+ position = vec4(screenPosition, 0.0, 1.0);
23057
+ } else if (u_surfaceMode > 0.5) {
23058
+ vec2 baseCorner = vec2(a_position.x, a_position.y);
23059
+ position = computeSurfaceCorner(baseCorner);
23060
+ } else {
23061
+ position = a_position;
23062
+ }
23063
+ position.xy = position.xy * u_screenToClipScale + u_screenToClipOffset;
23064
+ gl_Position = position;
22253
23065
  }
22254
23066
  `;
22255
23067
  const FRAGMENT_SHADER_SOURCE = `
@@ -22268,6 +23080,175 @@ const INITIAL_QUAD_VERTICES = new Float32Array(
22268
23080
  const QUAD_VERTEX_SCRATCH = new Float32Array(
22269
23081
  QUAD_VERTEX_COUNT * VERTEX_COMPONENT_COUNT
22270
23082
  );
23083
+ const DEBUG_OUTLINE_VERTEX_SHADER_SOURCE = `
23084
+ attribute vec4 a_position;
23085
+ uniform vec2 u_screenToClipScale;
23086
+ uniform vec2 u_screenToClipOffset;
23087
+ void main() {
23088
+ vec4 position = a_position;
23089
+ position.xy = position.xy * u_screenToClipScale + u_screenToClipOffset;
23090
+ gl_Position = position;
23091
+ }
23092
+ `;
23093
+ const DEBUG_OUTLINE_FRAGMENT_SHADER_SOURCE = `
23094
+ precision mediump float;
23095
+ uniform vec4 u_color;
23096
+ void main() {
23097
+ gl_FragColor = u_color;
23098
+ }
23099
+ `;
23100
+ const DEBUG_OUTLINE_VERTEX_COUNT = 4;
23101
+ const DEBUG_OUTLINE_POSITION_COMPONENT_COUNT = 4;
23102
+ const DEBUG_OUTLINE_VERTEX_STRIDE = DEBUG_OUTLINE_POSITION_COMPONENT_COUNT * FLOAT_SIZE;
23103
+ const DEBUG_OUTLINE_VERTEX_SCRATCH = new Float32Array(
23104
+ DEBUG_OUTLINE_VERTEX_COUNT * DEBUG_OUTLINE_POSITION_COMPONENT_COUNT
23105
+ );
23106
+ const DEBUG_OUTLINE_COLOR = [
23107
+ 1,
23108
+ 0,
23109
+ 0,
23110
+ 1
23111
+ ];
23112
+ const DEBUG_OUTLINE_CORNER_ORDER = [0, 1, 3, 2];
23113
+ const BILLBOARD_BASE_CORNERS = [
23114
+ [-1, 1],
23115
+ [1, 1],
23116
+ [-1, -1],
23117
+ [1, -1]
23118
+ ];
23119
+ const SURFACE_BASE_CORNERS = [
23120
+ [-1, 1],
23121
+ [1, 1],
23122
+ [-1, -1],
23123
+ [1, -1]
23124
+ ];
23125
+ const computeBillboardCornersShaderModel = ({
23126
+ centerX,
23127
+ centerY,
23128
+ halfWidth,
23129
+ halfHeight,
23130
+ anchor,
23131
+ rotationDeg
23132
+ }) => {
23133
+ var _a, _b;
23134
+ const anchorX = (_a = anchor == null ? void 0 : anchor.x) != null ? _a : 0;
23135
+ const anchorY = (_b = anchor == null ? void 0 : anchor.y) != null ? _b : 0;
23136
+ const rad = -rotationDeg * DEG2RAD;
23137
+ const cosR = Math.cos(rad);
23138
+ const sinR = Math.sin(rad);
23139
+ return BILLBOARD_BASE_CORNERS.map(([cornerXNorm, cornerYNorm], index) => {
23140
+ const cornerX = cornerXNorm * halfWidth;
23141
+ const cornerY = cornerYNorm * halfHeight;
23142
+ const shiftedX = cornerX - anchorX * halfWidth;
23143
+ const shiftedY = cornerY - anchorY * halfHeight;
23144
+ const rotatedX = shiftedX * cosR - shiftedY * sinR;
23145
+ const rotatedY = shiftedX * sinR + shiftedY * cosR;
23146
+ const [u, v] = UV_CORNERS[index];
23147
+ return {
23148
+ x: centerX + rotatedX,
23149
+ y: centerY - rotatedY,
23150
+ u,
23151
+ v
23152
+ };
23153
+ });
23154
+ };
23155
+ const calculateWorldToMercatorScale = (base, altitudeMeters) => {
23156
+ const origin = maplibreGlExports.MercatorCoordinate.fromLngLat(
23157
+ { lng: base.lng, lat: base.lat },
23158
+ altitudeMeters
23159
+ );
23160
+ const eastLngLat = applySurfaceDisplacement(base.lng, base.lat, 1, 0);
23161
+ const eastCoord = maplibreGlExports.MercatorCoordinate.fromLngLat(
23162
+ { lng: eastLngLat.lng, lat: eastLngLat.lat },
23163
+ altitudeMeters
23164
+ );
23165
+ const northLngLat = applySurfaceDisplacement(base.lng, base.lat, 0, 1);
23166
+ const northCoord = maplibreGlExports.MercatorCoordinate.fromLngLat(
23167
+ { lng: northLngLat.lng, lat: northLngLat.lat },
23168
+ altitudeMeters
23169
+ );
23170
+ return {
23171
+ east: eastCoord.x - origin.x,
23172
+ north: northCoord.y - origin.y
23173
+ };
23174
+ };
23175
+ const prepareSurfaceShaderInputs = (params) => {
23176
+ var _a, _b;
23177
+ const {
23178
+ baseLngLat,
23179
+ worldWidthMeters,
23180
+ worldHeightMeters,
23181
+ anchor,
23182
+ totalRotateDeg,
23183
+ offsetMeters,
23184
+ displacedCenter,
23185
+ altitudeMeters,
23186
+ depthBiasNdc,
23187
+ scaleAdjustment,
23188
+ centerDisplacement
23189
+ } = params;
23190
+ const halfSizeMeters = {
23191
+ east: worldWidthMeters / 2,
23192
+ north: worldHeightMeters / 2
23193
+ };
23194
+ const rotationRad = -totalRotateDeg * DEG2RAD;
23195
+ const sinR = Math.sin(rotationRad);
23196
+ const cosR = Math.cos(rotationRad);
23197
+ const mercatorCenter = maplibreGlExports.MercatorCoordinate.fromLngLat(
23198
+ { lng: displacedCenter.lng, lat: displacedCenter.lat },
23199
+ altitudeMeters
23200
+ );
23201
+ const worldToMercatorScale = calculateWorldToMercatorScale(
23202
+ displacedCenter,
23203
+ altitudeMeters
23204
+ );
23205
+ const cornerModel = computeSurfaceCornerShaderModel({
23206
+ baseLngLat,
23207
+ worldWidthMeters,
23208
+ worldHeightMeters,
23209
+ anchor,
23210
+ totalRotateDeg,
23211
+ offsetMeters
23212
+ });
23213
+ return {
23214
+ mercatorCenter: {
23215
+ x: mercatorCenter.x,
23216
+ y: mercatorCenter.y,
23217
+ z: (_a = mercatorCenter.z) != null ? _a : 0
23218
+ },
23219
+ worldToMercatorScale,
23220
+ halfSizeMeters,
23221
+ anchor,
23222
+ offsetMeters: {
23223
+ east: offsetMeters.east,
23224
+ north: offsetMeters.north
23225
+ },
23226
+ sinCos: { sin: sinR, cos: cosR },
23227
+ totalRotateDeg,
23228
+ depthBiasNdc,
23229
+ centerDisplacement: {
23230
+ east: centerDisplacement.east,
23231
+ north: centerDisplacement.north
23232
+ },
23233
+ baseLngLat,
23234
+ displacedCenter: {
23235
+ lng: displacedCenter.lng,
23236
+ lat: displacedCenter.lat,
23237
+ z: (_b = displacedCenter.z) != null ? _b : altitudeMeters
23238
+ },
23239
+ scaleAdjustment,
23240
+ corners: cornerModel.map((corner) => ({
23241
+ east: corner.east,
23242
+ north: corner.north,
23243
+ lng: corner.lng,
23244
+ lat: corner.lat
23245
+ })),
23246
+ clipCenter: { x: 0, y: 0, z: 0, w: 1 },
23247
+ clipBasisEast: { x: 0, y: 0, z: 0, w: 0 },
23248
+ clipBasisNorth: { x: 0, y: 0, z: 0, w: 0 },
23249
+ clipCorners: []
23250
+ };
23251
+ };
22271
23252
  const compileShader = (glContext, type, source) => {
22272
23253
  var _a;
22273
23254
  const shader = glContext.createShader(type);
@@ -22665,23 +23646,6 @@ const cloneOffset = (offset) => {
22665
23646
  offsetDeg: offset.offsetDeg
22666
23647
  };
22667
23648
  };
22668
- const updateImageDisplayedRotation = (image, optionsOverride) => {
22669
- const targetAngle = normaliseAngleDeg(
22670
- image.resolvedBaseRotateDeg + image.rotateDeg
22671
- );
22672
- const currentAngle = Number.isFinite(image.displayedRotateDeg) ? image.displayedRotateDeg : targetAngle;
22673
- const options = optionsOverride === void 0 ? image.rotationInterpolationOptions : optionsOverride;
22674
- const { nextAngleDeg, interpolationState } = resolveRotationTarget({
22675
- currentAngleDeg: currentAngle,
22676
- targetAngleDeg: targetAngle,
22677
- options: options != null ? options : void 0
22678
- });
22679
- image.displayedRotateDeg = nextAngleDeg;
22680
- image.rotationInterpolationState = interpolationState;
22681
- if (!interpolationState) {
22682
- image.displayedRotateDeg = targetAngle;
22683
- }
22684
- };
22685
23649
  const cloneInterpolationOptions = (options) => {
22686
23650
  return {
22687
23651
  mode: options.mode,
@@ -22689,40 +23653,40 @@ const cloneInterpolationOptions = (options) => {
22689
23653
  easing: options.easing
22690
23654
  };
22691
23655
  };
22692
- const cloneNumericInterpolationOptions = (options) => {
22693
- return {
22694
- durationMs: options.durationMs,
22695
- easing: options.easing
22696
- };
22697
- };
22698
23656
  const createImageStateFromInit = (imageInit, subLayer, order) => {
22699
23657
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
22700
23658
  const mode = (_a = imageInit.mode) != null ? _a : "surface";
22701
23659
  const autoRotationDefault = mode === "surface";
23660
+ const initialOffset = cloneOffset(imageInit.offset);
23661
+ const initialRotateDeg = normalizeAngleDeg((_b = imageInit.rotateDeg) != null ? _b : 0);
22702
23662
  const state = {
22703
23663
  subLayer,
22704
23664
  order,
22705
23665
  imageId: imageInit.imageId,
22706
23666
  mode,
22707
- opacity: (_b = imageInit.opacity) != null ? _b : 1,
22708
- scale: (_c = imageInit.scale) != null ? _c : 1,
23667
+ opacity: (_c = imageInit.opacity) != null ? _c : 1,
23668
+ scale: (_d = imageInit.scale) != null ? _d : 1,
22709
23669
  anchor: cloneAnchor(imageInit.anchor),
22710
- offset: cloneOffset(imageInit.offset),
22711
- rotateDeg: (_d = imageInit.rotateDeg) != null ? _d : 0,
22712
- displayedRotateDeg: normaliseAngleDeg((_e = imageInit.rotateDeg) != null ? _e : 0),
23670
+ offset: initialOffset,
23671
+ rotateDeg: (_e = imageInit.rotateDeg) != null ? _e : 0,
23672
+ displayedRotateDeg: initialRotateDeg,
22713
23673
  autoRotation: (_f = imageInit.autoRotation) != null ? _f : autoRotationDefault,
22714
23674
  autoRotationMinDistanceMeters: (_g = imageInit.autoRotationMinDistanceMeters) != null ? _g : DEFAULT_AUTO_ROTATION_MIN_DISTANCE_METERS,
22715
23675
  resolvedBaseRotateDeg: 0,
22716
23676
  originLocation: cloneOriginLocation(imageInit.originLocation),
22717
23677
  rotationInterpolationState: null,
22718
23678
  rotationInterpolationOptions: null,
22719
- offsetInterpolationState: null
23679
+ offsetDegInterpolationState: null,
23680
+ offsetMetersInterpolationState: null,
23681
+ lastCommandRotateDeg: initialRotateDeg,
23682
+ lastCommandOffsetDeg: initialOffset.offsetDeg,
23683
+ lastCommandOffsetMeters: initialOffset.offsetMeters
22720
23684
  };
22721
- const rotateInitOption = (_i = (_h = imageInit.rotationInterpolation) == null ? void 0 : _h.rotateDeg) != null ? _i : null;
23685
+ const rotateInitOption = (_i = (_h = imageInit.interpolation) == null ? void 0 : _h.rotateDeg) != null ? _i : null;
22722
23686
  if (rotateInitOption) {
22723
- state.rotationInterpolationOptions = cloneNumericInterpolationOptions(rotateInitOption);
23687
+ state.rotationInterpolationOptions = cloneInterpolationOptions(rotateInitOption);
22724
23688
  }
22725
- updateImageDisplayedRotation(state);
23689
+ syncImageRotationChannel(state);
22726
23690
  return state;
22727
23691
  };
22728
23692
  const createSpriteLayer = (options) => {
@@ -22732,6 +23696,7 @@ const createSpriteLayer = (options) => {
22732
23696
  const resolvedTextureFiltering = resolveTextureFilteringOptions(
22733
23697
  options == null ? void 0 : options.textureFiltering
22734
23698
  );
23699
+ const showDebugBounds = (options == null ? void 0 : options.showDebugBounds) === true;
22735
23700
  let gl = null;
22736
23701
  let map = null;
22737
23702
  let program = null;
@@ -22740,8 +23705,27 @@ const createSpriteLayer = (options) => {
22740
23705
  let attribUvLocation = -1;
22741
23706
  let uniformTextureLocation = null;
22742
23707
  let uniformOpacityLocation = null;
23708
+ let uniformScreenToClipScaleLocation = null;
23709
+ let uniformScreenToClipOffsetLocation = null;
23710
+ let uniformBillboardModeLocation = null;
23711
+ let uniformBillboardCenterLocation = null;
23712
+ let uniformBillboardHalfSizeLocation = null;
23713
+ let uniformBillboardAnchorLocation = null;
23714
+ let uniformBillboardSinCosLocation = null;
23715
+ let uniformSurfaceModeLocation = null;
23716
+ let uniformSurfaceDepthBiasLocation = null;
23717
+ let uniformSurfaceClipEnabledLocation = null;
23718
+ let uniformSurfaceClipCenterLocation = null;
23719
+ let uniformSurfaceClipBasisEastLocation = null;
23720
+ let uniformSurfaceClipBasisNorthLocation = null;
22743
23721
  let anisotropyExtension = null;
22744
23722
  let maxSupportedAnisotropy = 1;
23723
+ let debugProgram = null;
23724
+ let debugVertexBuffer = null;
23725
+ let debugAttribPositionLocation = -1;
23726
+ let debugUniformColorLocation = null;
23727
+ let debugUniformScreenToClipScaleLocation = null;
23728
+ let debugUniformScreenToClipOffsetLocation = null;
22745
23729
  const images = /* @__PURE__ */ new Map();
22746
23730
  const queuedTextureIds = /* @__PURE__ */ new Set();
22747
23731
  const queueTextureUpload = (image) => {
@@ -22754,6 +23738,347 @@ const createSpriteLayer = (options) => {
22754
23738
  queuedTextureIds.clear();
22755
23739
  };
22756
23740
  const sprites = /* @__PURE__ */ new Map();
23741
+ const resolveSpriteMercator = (sprite) => {
23742
+ var _a2;
23743
+ const location2 = sprite.currentLocation;
23744
+ const altitude = (_a2 = location2.z) != null ? _a2 : 0;
23745
+ if (sprite.cachedMercator && sprite.cachedMercatorLng === location2.lng && sprite.cachedMercatorLat === location2.lat && sprite.cachedMercatorZ === altitude) {
23746
+ return sprite.cachedMercator;
23747
+ }
23748
+ const mercator = maplibreGlExports.MercatorCoordinate.fromLngLat(
23749
+ { lng: location2.lng, lat: location2.lat },
23750
+ altitude
23751
+ );
23752
+ sprite.cachedMercator = mercator;
23753
+ sprite.cachedMercatorLng = location2.lng;
23754
+ sprite.cachedMercatorLat = location2.lat;
23755
+ sprite.cachedMercatorZ = altitude;
23756
+ return mercator;
23757
+ };
23758
+ const HIT_TEST_WORLD_BOUNDS = {
23759
+ x0: -180,
23760
+ y0: -90,
23761
+ x1: 180,
23762
+ y1: 90
23763
+ };
23764
+ const hitTestTree = createLooseQuadTree({
23765
+ bounds: HIT_TEST_WORLD_BOUNDS
23766
+ });
23767
+ let hitTestTreeItems = /* @__PURE__ */ new WeakMap();
23768
+ let isHitTestEnabled = true;
23769
+ const rectFromLngLatPoints = (points) => {
23770
+ let minLng = Number.POSITIVE_INFINITY;
23771
+ let maxLng = Number.NEGATIVE_INFINITY;
23772
+ let minLat = Number.POSITIVE_INFINITY;
23773
+ let maxLat = Number.NEGATIVE_INFINITY;
23774
+ for (const point of points) {
23775
+ if (!point || !Number.isFinite(point.lng) || !Number.isFinite(point.lat)) {
23776
+ continue;
23777
+ }
23778
+ if (point.lng < minLng) minLng = point.lng;
23779
+ if (point.lng > maxLng) maxLng = point.lng;
23780
+ if (point.lat < minLat) minLat = point.lat;
23781
+ if (point.lat > maxLat) maxLat = point.lat;
23782
+ }
23783
+ if (minLng === Number.POSITIVE_INFINITY || maxLng === Number.NEGATIVE_INFINITY || minLat === Number.POSITIVE_INFINITY || maxLat === Number.NEGATIVE_INFINITY) {
23784
+ return null;
23785
+ }
23786
+ return {
23787
+ x0: Math.max(
23788
+ HIT_TEST_WORLD_BOUNDS.x0,
23789
+ Math.min(minLng, HIT_TEST_WORLD_BOUNDS.x1)
23790
+ ),
23791
+ y0: Math.max(
23792
+ HIT_TEST_WORLD_BOUNDS.y0,
23793
+ Math.min(minLat, HIT_TEST_WORLD_BOUNDS.y1)
23794
+ ),
23795
+ x1: Math.max(
23796
+ HIT_TEST_WORLD_BOUNDS.x0,
23797
+ Math.min(maxLng, HIT_TEST_WORLD_BOUNDS.x1)
23798
+ ),
23799
+ y1: Math.max(
23800
+ HIT_TEST_WORLD_BOUNDS.y0,
23801
+ Math.min(maxLat, HIT_TEST_WORLD_BOUNDS.y1)
23802
+ )
23803
+ };
23804
+ };
23805
+ const rectFromRadiusMeters = (base, radiusMeters) => {
23806
+ if (!Number.isFinite(base.lng) || !Number.isFinite(base.lat) || !Number.isFinite(radiusMeters) || radiusMeters <= 0) {
23807
+ return null;
23808
+ }
23809
+ const cornerNE = applySurfaceDisplacement(
23810
+ base.lng,
23811
+ base.lat,
23812
+ radiusMeters,
23813
+ radiusMeters
23814
+ );
23815
+ const cornerSW = applySurfaceDisplacement(
23816
+ base.lng,
23817
+ base.lat,
23818
+ -radiusMeters,
23819
+ -radiusMeters
23820
+ );
23821
+ return rectFromLngLatPoints([cornerNE, cornerSW]);
23822
+ };
23823
+ const estimateSurfaceImageBounds = (sprite, image) => {
23824
+ var _a2, _b, _c, _d, _e;
23825
+ const mapInstance = map;
23826
+ if (!mapInstance) {
23827
+ return null;
23828
+ }
23829
+ const imageResource = images.get(image.imageId);
23830
+ if (!imageResource) {
23831
+ return null;
23832
+ }
23833
+ const baseLocation = sprite.currentLocation;
23834
+ const zoom = mapInstance.getZoom();
23835
+ const zoomScaleFactor = calculateZoomScaleFactor(zoom, resolvedScaling);
23836
+ const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
23837
+ zoom,
23838
+ baseLocation.lat
23839
+ );
23840
+ if (!Number.isFinite(metersPerPixelAtLat) || metersPerPixelAtLat <= 0) {
23841
+ return null;
23842
+ }
23843
+ const spriteMercator = resolveSpriteMercator(sprite);
23844
+ const perspectiveRatio = calculatePerspectiveRatio(
23845
+ mapInstance,
23846
+ baseLocation,
23847
+ spriteMercator
23848
+ );
23849
+ const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
23850
+ metersPerPixelAtLat,
23851
+ perspectiveRatio
23852
+ );
23853
+ if (!Number.isFinite(effectivePixelsPerMeter) || effectivePixelsPerMeter <= 0) {
23854
+ return null;
23855
+ }
23856
+ const imageScale = (_a2 = image.scale) != null ? _a2 : 1;
23857
+ const baseMetersPerPixel = resolvedScaling.metersPerPixel;
23858
+ const spriteMinPixel = resolvedScaling.spriteMinPixel;
23859
+ const spriteMaxPixel = resolvedScaling.spriteMaxPixel;
23860
+ const worldDims = calculateSurfaceWorldDimensions(
23861
+ imageResource.width,
23862
+ imageResource.height,
23863
+ baseMetersPerPixel,
23864
+ imageScale,
23865
+ zoomScaleFactor,
23866
+ {
23867
+ effectivePixelsPerMeter,
23868
+ spriteMinPixel,
23869
+ spriteMaxPixel
23870
+ }
23871
+ );
23872
+ if (worldDims.width <= 0 || worldDims.height <= 0) {
23873
+ return null;
23874
+ }
23875
+ const anchor = (_b = image.anchor) != null ? _b : DEFAULT_ANCHOR;
23876
+ const offsetDef = (_c = image.offset) != null ? _c : DEFAULT_IMAGE_OFFSET;
23877
+ const offsetMetersVec = calculateSurfaceOffsetMeters(
23878
+ offsetDef,
23879
+ imageScale,
23880
+ zoomScaleFactor,
23881
+ worldDims.scaleAdjustment
23882
+ );
23883
+ const totalRotateDeg = Number.isFinite(image.displayedRotateDeg) ? image.displayedRotateDeg : normalizeAngleDeg(
23884
+ ((_d = image.resolvedBaseRotateDeg) != null ? _d : 0) + ((_e = image.rotateDeg) != null ? _e : 0)
23885
+ );
23886
+ const cornerDisplacements = calculateSurfaceCornerDisplacements({
23887
+ worldWidthMeters: worldDims.width,
23888
+ worldHeightMeters: worldDims.height,
23889
+ anchor,
23890
+ totalRotateDeg,
23891
+ offsetMeters: offsetMetersVec
23892
+ });
23893
+ const corners = cornerDisplacements.map(
23894
+ (corner) => applySurfaceDisplacement(
23895
+ baseLocation.lng,
23896
+ baseLocation.lat,
23897
+ corner.east,
23898
+ corner.north
23899
+ )
23900
+ );
23901
+ return rectFromLngLatPoints(corners);
23902
+ };
23903
+ const estimateBillboardImageBounds = (sprite, image) => {
23904
+ var _a2, _b, _c, _d;
23905
+ const mapInstance = map;
23906
+ if (!mapInstance) {
23907
+ return null;
23908
+ }
23909
+ const imageResource = images.get(image.imageId);
23910
+ if (!imageResource) {
23911
+ return null;
23912
+ }
23913
+ const baseLocation = sprite.currentLocation;
23914
+ const zoom = mapInstance.getZoom();
23915
+ const zoomScaleFactor = calculateZoomScaleFactor(zoom, resolvedScaling);
23916
+ const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
23917
+ zoom,
23918
+ baseLocation.lat
23919
+ );
23920
+ if (!Number.isFinite(metersPerPixelAtLat) || metersPerPixelAtLat <= 0) {
23921
+ return null;
23922
+ }
23923
+ const spriteMercator = resolveSpriteMercator(sprite);
23924
+ const perspectiveRatio = calculatePerspectiveRatio(
23925
+ mapInstance,
23926
+ baseLocation,
23927
+ spriteMercator
23928
+ );
23929
+ const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
23930
+ metersPerPixelAtLat,
23931
+ perspectiveRatio
23932
+ );
23933
+ if (!Number.isFinite(effectivePixelsPerMeter) || effectivePixelsPerMeter <= 0) {
23934
+ return null;
23935
+ }
23936
+ const baseMetersPerPixel = resolvedScaling.metersPerPixel;
23937
+ const spriteMinPixel = resolvedScaling.spriteMinPixel;
23938
+ const spriteMaxPixel = resolvedScaling.spriteMaxPixel;
23939
+ const imageScale = (_a2 = image.scale) != null ? _a2 : 1;
23940
+ const totalRotateDeg = Number.isFinite(image.displayedRotateDeg) ? image.displayedRotateDeg : normalizeAngleDeg(
23941
+ ((_b = image.resolvedBaseRotateDeg) != null ? _b : 0) + ((_c = image.rotateDeg) != null ? _c : 0)
23942
+ );
23943
+ const pixelDims = calculateBillboardPixelDimensions(
23944
+ imageResource.width,
23945
+ imageResource.height,
23946
+ baseMetersPerPixel,
23947
+ imageScale,
23948
+ zoomScaleFactor,
23949
+ effectivePixelsPerMeter,
23950
+ spriteMinPixel,
23951
+ spriteMaxPixel
23952
+ );
23953
+ const halfWidthMeters = pixelDims.width / 2 / effectivePixelsPerMeter;
23954
+ const halfHeightMeters = pixelDims.height / 2 / effectivePixelsPerMeter;
23955
+ const anchorShift = calculateBillboardAnchorShiftPixels(
23956
+ pixelDims.width / 2,
23957
+ pixelDims.height / 2,
23958
+ image.anchor,
23959
+ totalRotateDeg
23960
+ );
23961
+ const offsetShift = calculateBillboardOffsetPixels(
23962
+ (_d = image.offset) != null ? _d : DEFAULT_IMAGE_OFFSET,
23963
+ imageScale,
23964
+ zoomScaleFactor,
23965
+ effectivePixelsPerMeter
23966
+ );
23967
+ const anchorShiftMeters = Math.hypot(anchorShift.x, anchorShift.y) / effectivePixelsPerMeter;
23968
+ const offsetShiftMeters = Math.hypot(offsetShift.x, offsetShift.y) / effectivePixelsPerMeter;
23969
+ const safetyRadius = Math.hypot(halfWidthMeters, halfHeightMeters) + anchorShiftMeters + offsetShiftMeters;
23970
+ return rectFromRadiusMeters(baseLocation, safetyRadius);
23971
+ };
23972
+ const estimateImageBounds = (sprite, image) => {
23973
+ if (image.opacity <= 0 || !sprite.isEnabled) {
23974
+ return null;
23975
+ }
23976
+ if (image.mode === "surface") {
23977
+ return estimateSurfaceImageBounds(sprite, image);
23978
+ }
23979
+ return estimateBillboardImageBounds(sprite, image);
23980
+ };
23981
+ const removeImageBoundsFromHitTestTree = (image) => {
23982
+ const handle = hitTestTreeItems.get(image);
23983
+ if (!handle) {
23984
+ return;
23985
+ }
23986
+ hitTestTree.remove(
23987
+ handle.rect.x0,
23988
+ handle.rect.y0,
23989
+ handle.rect.x1,
23990
+ handle.rect.y1,
23991
+ handle.item
23992
+ );
23993
+ hitTestTreeItems.delete(image);
23994
+ };
23995
+ const setItemRect = (item, rect) => {
23996
+ const mutable = item;
23997
+ mutable.x0 = rect.x0;
23998
+ mutable.y0 = rect.y0;
23999
+ mutable.x1 = rect.x1;
24000
+ mutable.y1 = rect.y1;
24001
+ };
24002
+ const registerImageBoundsInHitTestTree = (sprite, image) => {
24003
+ const existingHandle = hitTestTreeItems.get(image);
24004
+ if (!isHitTestEnabled) {
24005
+ if (existingHandle) {
24006
+ removeImageBoundsFromHitTestTree(image);
24007
+ }
24008
+ return;
24009
+ }
24010
+ const rect = estimateImageBounds(sprite, image);
24011
+ if (!rect) {
24012
+ if (existingHandle) {
24013
+ removeImageBoundsFromHitTestTree(image);
24014
+ }
24015
+ return;
24016
+ }
24017
+ if (!existingHandle) {
24018
+ const handle = {
24019
+ rect,
24020
+ item: {
24021
+ x0: rect.x0,
24022
+ y0: rect.y0,
24023
+ x1: rect.x1,
24024
+ y1: rect.y1,
24025
+ state: {
24026
+ sprite,
24027
+ image,
24028
+ drawIndex: 0
24029
+ }
24030
+ }
24031
+ };
24032
+ hitTestTree.add(handle.item);
24033
+ hitTestTreeItems.set(image, handle);
24034
+ return;
24035
+ }
24036
+ const currentRect = existingHandle.rect;
24037
+ const unchanged = currentRect.x0 === rect.x0 && currentRect.y0 === rect.y0 && currentRect.x1 === rect.x1 && currentRect.y1 === rect.y1;
24038
+ if (unchanged) {
24039
+ return;
24040
+ }
24041
+ const updated = hitTestTree.update(
24042
+ currentRect.x0,
24043
+ currentRect.y0,
24044
+ currentRect.x1,
24045
+ currentRect.y1,
24046
+ rect.x0,
24047
+ rect.y0,
24048
+ rect.x1,
24049
+ rect.y1,
24050
+ existingHandle.item
24051
+ );
24052
+ if (updated) {
24053
+ existingHandle.rect = rect;
24054
+ setItemRect(existingHandle.item, rect);
24055
+ return;
24056
+ }
24057
+ removeImageBoundsFromHitTestTree(image);
24058
+ const newHandle = {
24059
+ rect,
24060
+ item: {
24061
+ x0: rect.x0,
24062
+ y0: rect.y0,
24063
+ x1: rect.x1,
24064
+ y1: rect.y1,
24065
+ state: {
24066
+ sprite,
24067
+ image,
24068
+ drawIndex: 0
24069
+ }
24070
+ }
24071
+ };
24072
+ hitTestTree.add(newHandle.item);
24073
+ hitTestTreeItems.set(image, newHandle);
24074
+ };
24075
+ const refreshSpriteHitTestBounds = (sprite) => {
24076
+ sprite.images.forEach((orderMap) => {
24077
+ orderMap.forEach((image) => {
24078
+ registerImageBoundsInHitTestTree(sprite, image);
24079
+ });
24080
+ });
24081
+ };
22757
24082
  const getImageState = (sprite, subLayer, order) => {
22758
24083
  var _a2;
22759
24084
  return (
@@ -22786,34 +24111,38 @@ const createSpriteLayer = (options) => {
22786
24111
  return deleted;
22787
24112
  };
22788
24113
  const HIT_TEST_EPSILON = 1e-3;
22789
- const pointInTriangle = (point, a, b, c) => {
22790
- const v0x = c.x - a.x;
22791
- const v0y = c.y - a.y;
22792
- const v1x = b.x - a.x;
22793
- const v1y = b.y - a.y;
22794
- const v2x = point.x - a.x;
22795
- const v2y = point.y - a.y;
22796
- const dot00 = v0x * v0x + v0y * v0y;
22797
- const dot01 = v0x * v1x + v0y * v1y;
22798
- const dot02 = v0x * v2x + v0y * v2y;
22799
- const dot11 = v1x * v1x + v1y * v1y;
22800
- const dot12 = v1x * v2x + v1y * v2y;
22801
- const denom = dot00 * dot11 - dot01 * dot01;
22802
- if (Math.abs(denom) < HIT_TEST_EPSILON) {
22803
- return false;
24114
+ const pointInRenderedQuad = (point, corners) => {
24115
+ let hasPositiveCross = false;
24116
+ let hasNegativeCross = false;
24117
+ for (let i = 0; i < DEBUG_OUTLINE_CORNER_ORDER.length; i++) {
24118
+ const currentIndex = DEBUG_OUTLINE_CORNER_ORDER[i];
24119
+ const nextIndex = DEBUG_OUTLINE_CORNER_ORDER[(i + 1) % DEBUG_OUTLINE_CORNER_ORDER.length];
24120
+ const a = corners[currentIndex];
24121
+ const b = corners[nextIndex];
24122
+ const edgeX = b.x - a.x;
24123
+ const edgeY = b.y - a.y;
24124
+ const pointX = point.x - a.x;
24125
+ const pointY = point.y - a.y;
24126
+ const cross = edgeX * pointY - edgeY * pointX;
24127
+ if (Math.abs(cross) <= HIT_TEST_EPSILON) {
24128
+ continue;
24129
+ }
24130
+ if (cross > 0) {
24131
+ hasPositiveCross = true;
24132
+ } else {
24133
+ hasNegativeCross = true;
24134
+ }
24135
+ if (hasPositiveCross && hasNegativeCross) {
24136
+ return false;
24137
+ }
22804
24138
  }
22805
- const invDenom = 1 / denom;
22806
- const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
22807
- const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
22808
- const w = 1 - u - v;
22809
- return u >= -HIT_TEST_EPSILON && v >= -HIT_TEST_EPSILON && w >= -HIT_TEST_EPSILON;
24139
+ return true;
22810
24140
  };
22811
- const pointInQuad = (point, corners) => pointInTriangle(point, corners[0], corners[1], corners[2]) || pointInTriangle(point, corners[0], corners[2], corners[3]);
22812
24141
  const isPointInsideHitEntry = (entry, point) => {
22813
24142
  if (point.x < entry.minX - HIT_TEST_EPSILON || point.x > entry.maxX + HIT_TEST_EPSILON || point.y < entry.minY - HIT_TEST_EPSILON || point.y > entry.maxY + HIT_TEST_EPSILON) {
22814
24143
  return false;
22815
24144
  }
22816
- return pointInQuad(point, entry.corners);
24145
+ return pointInRenderedQuad(point, entry.corners);
22817
24146
  };
22818
24147
  const buildSortedSubLayerBuckets = (entries) => {
22819
24148
  const buckets = /* @__PURE__ */ new Map();
@@ -22872,7 +24201,7 @@ const createSpriteLayer = (options) => {
22872
24201
  baseY = refCenter.y;
22873
24202
  }
22874
24203
  }
22875
- const totalRotDeg = Number.isFinite(img.displayedRotateDeg) ? img.displayedRotateDeg : normaliseAngleDeg(
24204
+ const totalRotDeg = Number.isFinite(img.displayedRotateDeg) ? img.displayedRotateDeg : normalizeAngleDeg(
22876
24205
  ((_d = img.resolvedBaseRotateDeg) != null ? _d : 0) + ((_e = img.rotateDeg) != null ? _e : 0)
22877
24206
  );
22878
24207
  const imageScaleLocal = (_f = img.scale) != null ? _f : 1;
@@ -22955,6 +24284,7 @@ const createSpriteLayer = (options) => {
22955
24284
  };
22956
24285
  const renderTargetEntries = [];
22957
24286
  const hitTestEntries = [];
24287
+ let hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
22958
24288
  const ensureHitTestCorners = (imageEntry) => {
22959
24289
  if (!imageEntry.hitTestCorners) {
22960
24290
  imageEntry.hitTestCorners = [
@@ -22966,7 +24296,10 @@ const createSpriteLayer = (options) => {
22966
24296
  }
22967
24297
  return imageEntry.hitTestCorners;
22968
24298
  };
22969
- const registerHitTestEntry = (spriteEntry, imageEntry, screenCorners) => {
24299
+ const registerHitTestEntry = (spriteEntry, imageEntry, screenCorners, drawIndex) => {
24300
+ if (!isHitTestEnabled) {
24301
+ return;
24302
+ }
22970
24303
  const corners = screenCorners;
22971
24304
  let minX = corners[0].x;
22972
24305
  let maxX = corners[0].x;
@@ -22979,7 +24312,7 @@ const createSpriteLayer = (options) => {
22979
24312
  if (corner.y < minY) minY = corner.y;
22980
24313
  if (corner.y > maxY) maxY = corner.y;
22981
24314
  }
22982
- hitTestEntries.push({
24315
+ const entry = {
22983
24316
  sprite: spriteEntry,
22984
24317
  image: imageEntry,
22985
24318
  corners,
@@ -22987,16 +24320,82 @@ const createSpriteLayer = (options) => {
22987
24320
  maxX,
22988
24321
  minY,
22989
24322
  maxY
22990
- });
24323
+ };
24324
+ hitTestEntries.push(entry);
24325
+ hitTestEntryByImage.set(imageEntry, entry);
24326
+ const handle = hitTestTreeItems.get(imageEntry);
24327
+ if (handle) {
24328
+ handle.item.state.drawIndex = drawIndex;
24329
+ }
22991
24330
  };
22992
- const findTopmostHitEntry = (point) => {
24331
+ const findTopmostHitEntryLinear = (point) => {
22993
24332
  for (let i = hitTestEntries.length - 1; i >= 0; i--) {
22994
24333
  const entry = hitTestEntries[i];
22995
24334
  if (isPointInsideHitEntry(entry, point)) {
22996
24335
  return entry;
22997
24336
  }
22998
24337
  }
22999
- return null;
24338
+ return null;
24339
+ };
24340
+ const findTopmostHitEntry = (point) => {
24341
+ if (!isHitTestEnabled) {
24342
+ return null;
24343
+ }
24344
+ const mapInstance = map;
24345
+ if (!mapInstance) {
24346
+ return findTopmostHitEntryLinear(point);
24347
+ }
24348
+ const centerLngLat = mapInstance.unproject([point.x, point.y]);
24349
+ if (!centerLngLat) {
24350
+ return findTopmostHitEntryLinear(point);
24351
+ }
24352
+ const searchPoints = [
24353
+ { lng: centerLngLat.lng, lat: centerLngLat.lat }
24354
+ ];
24355
+ const radius = HIT_TEST_QUERY_RADIUS_PIXELS;
24356
+ const offsets = [
24357
+ [point.x - radius, point.y - radius],
24358
+ [point.x + radius, point.y - radius],
24359
+ [point.x - radius, point.y + radius],
24360
+ [point.x + radius, point.y + radius]
24361
+ ];
24362
+ for (const [x, y] of offsets) {
24363
+ const lngLat = mapInstance.unproject([x, y]);
24364
+ if (lngLat) {
24365
+ searchPoints.push({ lng: lngLat.lng, lat: lngLat.lat });
24366
+ }
24367
+ }
24368
+ const searchRect = rectFromLngLatPoints(searchPoints);
24369
+ if (!searchRect) {
24370
+ return findTopmostHitEntryLinear(point);
24371
+ }
24372
+ const candidates = hitTestTree.lookup(
24373
+ searchRect.x0,
24374
+ searchRect.y0,
24375
+ searchRect.x1,
24376
+ searchRect.y1
24377
+ );
24378
+ if (candidates.length === 0) {
24379
+ return findTopmostHitEntryLinear(point);
24380
+ }
24381
+ candidates.sort((a, b) => a.state.drawIndex - b.state.drawIndex);
24382
+ const seenImages = /* @__PURE__ */ new Set();
24383
+ for (let i = candidates.length - 1; i >= 0; i--) {
24384
+ const candidate = candidates[i];
24385
+ const image = candidate.state.image;
24386
+ if (seenImages.has(image)) {
24387
+ continue;
24388
+ }
24389
+ seenImages.add(image);
24390
+ const entry = hitTestEntryByImage.get(image);
24391
+ if (!entry) {
24392
+ continue;
24393
+ }
24394
+ if (isPointInsideHitEntry(entry, point)) {
24395
+ return entry;
24396
+ }
24397
+ }
24398
+ return findTopmostHitEntryLinear(point);
23000
24399
  };
23001
24400
  const eventListeners = /* @__PURE__ */ new Map();
23002
24401
  const getListenerSet = (type) => {
@@ -23022,13 +24421,15 @@ const createSpriteLayer = (options) => {
23022
24421
  };
23023
24422
  let canvasElement = null;
23024
24423
  const inputListenerDisposers = [];
23025
- const hasSpriteClickListeners = () => {
24424
+ const hasSpriteListeners = (type) => {
23026
24425
  var _a2, _b;
23027
24426
  return (
23028
24427
  // Treat missing listener sets as zero, otherwise check the registered count.
23029
- ((_b = (_a2 = eventListeners.get("spriteclick")) == null ? void 0 : _a2.size) != null ? _b : 0) > 0
24428
+ ((_b = (_a2 = eventListeners.get(type)) == null ? void 0 : _a2.size) != null ? _b : 0) > 0
23030
24429
  );
23031
24430
  };
24431
+ const hasSpriteClickListeners = () => hasSpriteListeners("spriteclick");
24432
+ const hasSpriteHoverListeners = () => hasSpriteListeners("spritehover");
23032
24433
  const resolveScreenPointFromEvent = (nativeEvent) => {
23033
24434
  var _a2, _b, _c, _d;
23034
24435
  if (!canvasElement) {
@@ -23053,20 +24454,31 @@ const createSpriteLayer = (options) => {
23053
24454
  const mouseLike = nativeEvent;
23054
24455
  return toScreenPoint(mouseLike.clientX, mouseLike.clientY);
23055
24456
  };
24457
+ const resolveSpriteEventPayload = (hitEntry) => {
24458
+ var _a2, _b;
24459
+ if (!hitEntry) {
24460
+ return {
24461
+ sprite: void 0,
24462
+ image: void 0
24463
+ };
24464
+ }
24465
+ const spriteState = getSpriteState(hitEntry.sprite.spriteId);
24466
+ 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;
24467
+ return {
24468
+ sprite: spriteState,
24469
+ image: imageState
24470
+ };
24471
+ };
23056
24472
  const dispatchSpriteClick = (hitEntry, screenPoint, originalEvent) => {
23057
24473
  const listeners = eventListeners.get("spriteclick");
23058
24474
  if (!listeners || listeners.size === 0) {
23059
24475
  return;
23060
24476
  }
23061
- const spriteState = getSpriteState(hitEntry.sprite.spriteId);
23062
- if (!spriteState) {
23063
- return;
23064
- }
23065
- const imageState = hitEntry.image;
24477
+ const payload = resolveSpriteEventPayload(hitEntry);
23066
24478
  const clickEvent = {
23067
24479
  type: "spriteclick",
23068
- sprite: spriteState,
23069
- image: imageState,
24480
+ sprite: payload.sprite,
24481
+ image: payload.image,
23070
24482
  screenPoint,
23071
24483
  originalEvent
23072
24484
  };
@@ -23074,22 +24486,50 @@ const createSpriteLayer = (options) => {
23074
24486
  listener(clickEvent);
23075
24487
  });
23076
24488
  };
23077
- const processInteractionEvent = (nativeEvent) => {
24489
+ const dispatchSpriteHover = (hitEntry, screenPoint, originalEvent) => {
24490
+ const listeners = eventListeners.get("spritehover");
24491
+ if (!listeners || listeners.size === 0) {
24492
+ return;
24493
+ }
24494
+ const payload = resolveSpriteEventPayload(hitEntry);
24495
+ const hoverEvent = {
24496
+ type: "spritehover",
24497
+ sprite: payload.sprite,
24498
+ image: payload.image,
24499
+ screenPoint,
24500
+ originalEvent
24501
+ };
24502
+ listeners.forEach((listener) => {
24503
+ listener(hoverEvent);
24504
+ });
24505
+ };
24506
+ const resolveHitTestResult = (nativeEvent) => {
24507
+ const screenPoint = resolveScreenPointFromEvent(nativeEvent);
24508
+ if (!screenPoint) {
24509
+ return null;
24510
+ }
24511
+ const hitEntry = findTopmostHitEntry(screenPoint);
24512
+ return { hitEntry: hitEntry != null ? hitEntry : null, screenPoint };
24513
+ };
24514
+ const processClickEvent = (nativeEvent) => {
23078
24515
  if (!hasSpriteClickListeners()) {
23079
24516
  return;
23080
24517
  }
23081
- if (hitTestEntries.length === 0) {
24518
+ const hitResult = resolveHitTestResult(nativeEvent);
24519
+ if (!hitResult || !hitResult.hitEntry) {
23082
24520
  return;
23083
24521
  }
23084
- const screenPoint = resolveScreenPointFromEvent(nativeEvent);
23085
- if (!screenPoint) {
24522
+ dispatchSpriteClick(hitResult.hitEntry, hitResult.screenPoint, nativeEvent);
24523
+ };
24524
+ const processHoverEvent = (nativeEvent) => {
24525
+ if (!hasSpriteHoverListeners()) {
23086
24526
  return;
23087
24527
  }
23088
- const hitEntry = findTopmostHitEntry(screenPoint);
23089
- if (!hitEntry) {
24528
+ const hitResult = resolveHitTestResult(nativeEvent);
24529
+ if (!hitResult) {
23090
24530
  return;
23091
24531
  }
23092
- dispatchSpriteClick(hitEntry, screenPoint, nativeEvent);
24532
+ dispatchSpriteHover(hitResult.hitEntry, hitResult.screenPoint, nativeEvent);
23093
24533
  };
23094
24534
  const ensureTextures = () => {
23095
24535
  if (!gl) {
@@ -23242,24 +24682,42 @@ const createSpriteLayer = (options) => {
23242
24682
  const supportsPointerEvents = typeof window !== "undefined" && "PointerEvent" in window;
23243
24683
  if (canvasElement) {
23244
24684
  if (supportsPointerEvents) {
23245
- const pointerListener = (event) => {
24685
+ const pointerUpListener = (event) => {
23246
24686
  if (event.pointerType === "mouse" && event.button !== 0) {
23247
24687
  return;
23248
24688
  }
23249
- processInteractionEvent(event);
24689
+ processClickEvent(event);
24690
+ };
24691
+ canvasElement.addEventListener("pointerup", pointerUpListener, {
24692
+ passive: true
24693
+ });
24694
+ registerDisposer(() => {
24695
+ canvasElement == null ? void 0 : canvasElement.removeEventListener("pointerup", pointerUpListener);
24696
+ });
24697
+ const pointerMoveListener = (event) => {
24698
+ if (!event.isPrimary) {
24699
+ return;
24700
+ }
24701
+ if (event.pointerType === "touch") {
24702
+ return;
24703
+ }
24704
+ processHoverEvent(event);
23250
24705
  };
23251
- canvasElement.addEventListener("pointerup", pointerListener, {
24706
+ canvasElement.addEventListener("pointermove", pointerMoveListener, {
23252
24707
  passive: true
23253
24708
  });
23254
24709
  registerDisposer(() => {
23255
- canvasElement == null ? void 0 : canvasElement.removeEventListener("pointerup", pointerListener);
24710
+ canvasElement == null ? void 0 : canvasElement.removeEventListener(
24711
+ "pointermove",
24712
+ pointerMoveListener
24713
+ );
23256
24714
  });
23257
24715
  } else {
23258
24716
  const clickListener = (event) => {
23259
24717
  if (event.button !== 0) {
23260
24718
  return;
23261
24719
  }
23262
- processInteractionEvent(event);
24720
+ processClickEvent(event);
23263
24721
  };
23264
24722
  canvasElement.addEventListener("click", clickListener, {
23265
24723
  passive: true
@@ -23268,7 +24726,7 @@ const createSpriteLayer = (options) => {
23268
24726
  canvasElement == null ? void 0 : canvasElement.removeEventListener("click", clickListener);
23269
24727
  });
23270
24728
  const touchListener = (event) => {
23271
- processInteractionEvent(event);
24729
+ processClickEvent(event);
23272
24730
  };
23273
24731
  canvasElement.addEventListener("touchend", touchListener, {
23274
24732
  passive: true
@@ -23276,6 +24734,15 @@ const createSpriteLayer = (options) => {
23276
24734
  registerDisposer(() => {
23277
24735
  canvasElement == null ? void 0 : canvasElement.removeEventListener("touchend", touchListener);
23278
24736
  });
24737
+ const mouseMoveListener = (event) => {
24738
+ processHoverEvent(event);
24739
+ };
24740
+ canvasElement.addEventListener("mousemove", mouseMoveListener, {
24741
+ passive: true
24742
+ });
24743
+ registerDisposer(() => {
24744
+ canvasElement == null ? void 0 : canvasElement.removeEventListener("mousemove", mouseMoveListener);
24745
+ });
23279
24746
  }
23280
24747
  }
23281
24748
  const buffer = glContext.createBuffer();
@@ -23312,7 +24779,59 @@ const createSpriteLayer = (options) => {
23312
24779
  shaderProgram,
23313
24780
  "u_opacity"
23314
24781
  );
23315
- if (!uniformTextureLocation || !uniformOpacityLocation) {
24782
+ uniformScreenToClipScaleLocation = glContext.getUniformLocation(
24783
+ shaderProgram,
24784
+ "u_screenToClipScale"
24785
+ );
24786
+ uniformScreenToClipOffsetLocation = glContext.getUniformLocation(
24787
+ shaderProgram,
24788
+ "u_screenToClipOffset"
24789
+ );
24790
+ uniformBillboardModeLocation = glContext.getUniformLocation(
24791
+ shaderProgram,
24792
+ "u_billboardMode"
24793
+ );
24794
+ uniformBillboardCenterLocation = glContext.getUniformLocation(
24795
+ shaderProgram,
24796
+ "u_billboardCenter"
24797
+ );
24798
+ uniformBillboardHalfSizeLocation = glContext.getUniformLocation(
24799
+ shaderProgram,
24800
+ "u_billboardHalfSize"
24801
+ );
24802
+ uniformBillboardAnchorLocation = glContext.getUniformLocation(
24803
+ shaderProgram,
24804
+ "u_billboardAnchor"
24805
+ );
24806
+ uniformBillboardSinCosLocation = glContext.getUniformLocation(
24807
+ shaderProgram,
24808
+ "u_billboardSinCos"
24809
+ );
24810
+ uniformSurfaceModeLocation = glContext.getUniformLocation(
24811
+ shaderProgram,
24812
+ "u_surfaceMode"
24813
+ );
24814
+ uniformSurfaceDepthBiasLocation = glContext.getUniformLocation(
24815
+ shaderProgram,
24816
+ "u_surfaceDepthBias"
24817
+ );
24818
+ uniformSurfaceClipEnabledLocation = glContext.getUniformLocation(
24819
+ shaderProgram,
24820
+ "u_surfaceClipEnabled"
24821
+ );
24822
+ uniformSurfaceClipCenterLocation = glContext.getUniformLocation(
24823
+ shaderProgram,
24824
+ "u_surfaceClipCenter"
24825
+ );
24826
+ uniformSurfaceClipBasisEastLocation = glContext.getUniformLocation(
24827
+ shaderProgram,
24828
+ "u_surfaceClipBasisEast"
24829
+ );
24830
+ uniformSurfaceClipBasisNorthLocation = glContext.getUniformLocation(
24831
+ shaderProgram,
24832
+ "u_surfaceClipBasisNorth"
24833
+ );
24834
+ if (!uniformTextureLocation || !uniformOpacityLocation || !uniformScreenToClipScaleLocation || !uniformScreenToClipOffsetLocation || !uniformBillboardModeLocation || !uniformBillboardCenterLocation || !uniformBillboardHalfSizeLocation || !uniformBillboardAnchorLocation || !uniformBillboardSinCosLocation || !uniformSurfaceModeLocation || !uniformSurfaceDepthBiasLocation || !uniformSurfaceClipEnabledLocation || !uniformSurfaceClipCenterLocation || !uniformSurfaceClipBasisEastLocation || !uniformSurfaceClipBasisNorthLocation) {
23316
24835
  throw new Error("Failed to acquire uniform locations.");
23317
24836
  }
23318
24837
  glContext.enableVertexAttribArray(attribPositionLocation);
@@ -23335,7 +24854,80 @@ const createSpriteLayer = (options) => {
23335
24854
  );
23336
24855
  glContext.uniform1i(uniformTextureLocation, 0);
23337
24856
  glContext.uniform1f(uniformOpacityLocation, 1);
24857
+ glContext.uniform2f(uniformScreenToClipScaleLocation, 1, 1);
24858
+ glContext.uniform2f(uniformScreenToClipOffsetLocation, 0, 0);
24859
+ glContext.uniform1f(uniformSurfaceClipEnabledLocation, 0);
24860
+ glContext.uniform4f(uniformSurfaceClipCenterLocation, 0, 0, 0, 1);
24861
+ glContext.uniform4f(
24862
+ uniformSurfaceClipBasisEastLocation,
24863
+ 0,
24864
+ 0,
24865
+ 0,
24866
+ 0
24867
+ );
24868
+ glContext.uniform4f(
24869
+ uniformSurfaceClipBasisNorthLocation,
24870
+ 0,
24871
+ 0,
24872
+ 0,
24873
+ 0
24874
+ );
24875
+ glContext.uniform1f(uniformBillboardModeLocation, 0);
24876
+ glContext.uniform2f(uniformBillboardCenterLocation, 0, 0);
24877
+ glContext.uniform2f(uniformBillboardHalfSizeLocation, 0, 0);
24878
+ glContext.uniform2f(uniformBillboardAnchorLocation, 0, 0);
24879
+ glContext.uniform2f(uniformBillboardSinCosLocation, 0, 1);
24880
+ glContext.uniform1f(uniformSurfaceModeLocation, 0);
24881
+ glContext.uniform1f(uniformSurfaceDepthBiasLocation, 0);
23338
24882
  glContext.bindBuffer(glContext.ARRAY_BUFFER, null);
24883
+ if (showDebugBounds) {
24884
+ const debugShaderProgram = createShaderProgram(
24885
+ glContext,
24886
+ DEBUG_OUTLINE_VERTEX_SHADER_SOURCE,
24887
+ DEBUG_OUTLINE_FRAGMENT_SHADER_SOURCE
24888
+ );
24889
+ debugProgram = debugShaderProgram;
24890
+ debugAttribPositionLocation = glContext.getAttribLocation(
24891
+ debugShaderProgram,
24892
+ "a_position"
24893
+ );
24894
+ if (debugAttribPositionLocation === -1) {
24895
+ throw new Error("Failed to acquire debug attribute location.");
24896
+ }
24897
+ const colorLocation = glContext.getUniformLocation(
24898
+ debugShaderProgram,
24899
+ "u_color"
24900
+ );
24901
+ if (!colorLocation) {
24902
+ throw new Error("Failed to acquire debug color uniform.");
24903
+ }
24904
+ debugUniformColorLocation = colorLocation;
24905
+ debugUniformScreenToClipScaleLocation = glContext.getUniformLocation(
24906
+ debugShaderProgram,
24907
+ "u_screenToClipScale"
24908
+ );
24909
+ debugUniformScreenToClipOffsetLocation = glContext.getUniformLocation(
24910
+ debugShaderProgram,
24911
+ "u_screenToClipOffset"
24912
+ );
24913
+ if (!debugUniformScreenToClipScaleLocation || !debugUniformScreenToClipOffsetLocation) {
24914
+ throw new Error("Failed to acquire debug screen-to-clip uniforms.");
24915
+ }
24916
+ glContext.uniform2f(debugUniformScreenToClipScaleLocation, 1, 1);
24917
+ glContext.uniform2f(debugUniformScreenToClipOffsetLocation, 0, 0);
24918
+ const outlineBuffer = glContext.createBuffer();
24919
+ if (!outlineBuffer) {
24920
+ throw new Error("Failed to create debug vertex buffer.");
24921
+ }
24922
+ debugVertexBuffer = outlineBuffer;
24923
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, outlineBuffer);
24924
+ glContext.bufferData(
24925
+ glContext.ARRAY_BUFFER,
24926
+ DEBUG_OUTLINE_VERTEX_SCRATCH,
24927
+ glContext.DYNAMIC_DRAW
24928
+ );
24929
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, null);
24930
+ }
23339
24931
  scheduleRender();
23340
24932
  };
23341
24933
  const onRemove = () => {
@@ -23343,6 +24935,9 @@ const createSpriteLayer = (options) => {
23343
24935
  inputListenerDisposers.length = 0;
23344
24936
  canvasElement = null;
23345
24937
  hitTestEntries.length = 0;
24938
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24939
+ hitTestTree.clear();
24940
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
23346
24941
  const glContext = gl;
23347
24942
  if (glContext) {
23348
24943
  images.forEach((image) => {
@@ -23355,9 +24950,15 @@ const createSpriteLayer = (options) => {
23355
24950
  if (vertexBuffer) {
23356
24951
  glContext.deleteBuffer(vertexBuffer);
23357
24952
  }
24953
+ if (debugVertexBuffer) {
24954
+ glContext.deleteBuffer(debugVertexBuffer);
24955
+ }
23358
24956
  if (program) {
23359
24957
  glContext.deleteProgram(program);
23360
24958
  }
24959
+ if (debugProgram) {
24960
+ glContext.deleteProgram(debugProgram);
24961
+ }
23361
24962
  }
23362
24963
  eventListeners.forEach((set) => set.clear());
23363
24964
  eventListeners.clear();
@@ -23365,20 +24966,36 @@ const createSpriteLayer = (options) => {
23365
24966
  map = null;
23366
24967
  program = null;
23367
24968
  vertexBuffer = null;
24969
+ debugProgram = null;
24970
+ debugVertexBuffer = null;
23368
24971
  attribPositionLocation = -1;
23369
24972
  attribUvLocation = -1;
24973
+ debugAttribPositionLocation = -1;
23370
24974
  uniformTextureLocation = null;
23371
24975
  uniformOpacityLocation = null;
24976
+ uniformScreenToClipScaleLocation = null;
24977
+ uniformScreenToClipOffsetLocation = null;
24978
+ uniformBillboardModeLocation = null;
24979
+ uniformBillboardCenterLocation = null;
24980
+ uniformBillboardHalfSizeLocation = null;
24981
+ uniformBillboardAnchorLocation = null;
24982
+ uniformBillboardSinCosLocation = null;
24983
+ uniformSurfaceModeLocation = null;
24984
+ uniformSurfaceDepthBiasLocation = null;
24985
+ debugUniformColorLocation = null;
24986
+ debugUniformScreenToClipScaleLocation = null;
24987
+ debugUniformScreenToClipOffsetLocation = null;
23372
24988
  anisotropyExtension = null;
23373
24989
  maxSupportedAnisotropy = 1;
23374
24990
  };
23375
24991
  const render = (glContext, _options) => {
23376
24992
  hitTestEntries.length = 0;
24993
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
23377
24994
  const mapInstance = map;
23378
24995
  if (!mapInstance || !program || !vertexBuffer) {
23379
24996
  return;
23380
24997
  }
23381
- if (!uniformOpacityLocation || !uniformTextureLocation) {
24998
+ if (!uniformOpacityLocation || !uniformTextureLocation || !uniformScreenToClipScaleLocation || !uniformScreenToClipOffsetLocation) {
23382
24999
  return;
23383
25000
  }
23384
25001
  const timestamp = typeof performance !== "undefined" && typeof performance.now === "function" ? (
@@ -23411,41 +25028,8 @@ const createSpriteLayer = (options) => {
23411
25028
  }
23412
25029
  sprite.images.forEach((orderMap) => {
23413
25030
  orderMap.forEach((image) => {
23414
- const rotationState = image.rotationInterpolationState;
23415
- if (rotationState) {
23416
- const evaluation = evaluateNumericInterpolation({
23417
- state: rotationState,
23418
- timestamp
23419
- });
23420
- if (rotationState.startTimestamp < 0) {
23421
- rotationState.startTimestamp = evaluation.effectiveStartTimestamp;
23422
- }
23423
- image.displayedRotateDeg = normaliseAngleDeg(evaluation.value);
23424
- if (evaluation.completed) {
23425
- image.displayedRotateDeg = normaliseAngleDeg(
23426
- rotationState.finalValue
23427
- );
23428
- image.rotationInterpolationState = null;
23429
- } else {
23430
- hasActiveInterpolation = true;
23431
- }
23432
- }
23433
- const offsetState = image.offsetInterpolationState;
23434
- if (offsetState) {
23435
- const evaluation = evaluateNumericInterpolation({
23436
- state: offsetState,
23437
- timestamp
23438
- });
23439
- if (offsetState.startTimestamp < 0) {
23440
- offsetState.startTimestamp = evaluation.effectiveStartTimestamp;
23441
- }
23442
- image.offset.offsetDeg = evaluation.value;
23443
- if (evaluation.completed) {
23444
- image.offset.offsetDeg = offsetState.finalValue;
23445
- image.offsetInterpolationState = null;
23446
- } else {
23447
- hasActiveInterpolation = true;
23448
- }
25031
+ if (stepSpriteImageInterpolations(image, timestamp)) {
25032
+ hasActiveInterpolation = true;
23449
25033
  }
23450
25034
  });
23451
25035
  });
@@ -23463,6 +25047,17 @@ const createSpriteLayer = (options) => {
23463
25047
  const drawingBufferWidth = glContext.drawingBufferWidth;
23464
25048
  const drawingBufferHeight = glContext.drawingBufferHeight;
23465
25049
  const pixelRatio = drawingBufferWidth / cssWidth;
25050
+ if (drawingBufferWidth === 0 || drawingBufferHeight === 0) {
25051
+ return;
25052
+ }
25053
+ const screenToClipScaleX = 2 * pixelRatio / drawingBufferWidth;
25054
+ const screenToClipScaleY = -2 * pixelRatio / drawingBufferHeight;
25055
+ const screenToClipOffsetX = -1;
25056
+ const screenToClipOffsetY = 1;
25057
+ const identityScaleX = 1;
25058
+ const identityScaleY = 1;
25059
+ const identityOffsetX = 0;
25060
+ const identityOffsetY = 0;
23466
25061
  const zoom = mapInstance.getZoom();
23467
25062
  const zoomScaleFactor = calculateZoomScaleFactor(zoom, resolvedScaling);
23468
25063
  const baseMetersPerPixel = resolvedScaling.metersPerPixel;
@@ -23497,18 +25092,84 @@ const createSpriteLayer = (options) => {
23497
25092
  UV_OFFSET
23498
25093
  );
23499
25094
  glContext.uniform1i(uniformTextureLocation, 0);
25095
+ const screenToClipScaleLocation = uniformScreenToClipScaleLocation;
25096
+ const screenToClipOffsetLocation = uniformScreenToClipOffsetLocation;
25097
+ let currentScaleX = Number.NaN;
25098
+ let currentScaleY = Number.NaN;
25099
+ let currentOffsetX = Number.NaN;
25100
+ let currentOffsetY = Number.NaN;
25101
+ const applyScreenToClipUniforms = (scaleX, scaleY, offsetX, offsetY) => {
25102
+ if (scaleX !== currentScaleX || scaleY !== currentScaleY || offsetX !== currentOffsetX || offsetY !== currentOffsetY) {
25103
+ glContext.uniform2f(screenToClipScaleLocation, scaleX, scaleY);
25104
+ glContext.uniform2f(screenToClipOffsetLocation, offsetX, offsetY);
25105
+ currentScaleX = scaleX;
25106
+ currentScaleY = scaleY;
25107
+ currentOffsetX = offsetX;
25108
+ currentOffsetY = offsetY;
25109
+ }
25110
+ };
25111
+ let currentSurfaceMode = Number.NaN;
25112
+ const applySurfaceMode = (enabled) => {
25113
+ if (!uniformSurfaceModeLocation) {
25114
+ return;
25115
+ }
25116
+ const value = enabled ? 1 : 0;
25117
+ if (value !== currentSurfaceMode) {
25118
+ glContext.uniform1f(uniformSurfaceModeLocation, value);
25119
+ currentSurfaceMode = value;
25120
+ }
25121
+ };
25122
+ let currentSurfaceClipEnabled = Number.NaN;
25123
+ const applySurfaceClipUniforms = (enabled, inputs) => {
25124
+ if (!uniformSurfaceClipEnabledLocation || !uniformSurfaceClipCenterLocation || !uniformSurfaceClipBasisEastLocation || !uniformSurfaceClipBasisNorthLocation) {
25125
+ return;
25126
+ }
25127
+ const value = enabled ? 1 : 0;
25128
+ if (value !== currentSurfaceClipEnabled) {
25129
+ glContext.uniform1f(uniformSurfaceClipEnabledLocation, value);
25130
+ currentSurfaceClipEnabled = value;
25131
+ }
25132
+ const clipCenter = enabled && inputs ? inputs.clipCenter : { x: 0, y: 0, z: 0, w: 1 };
25133
+ glContext.uniform4f(
25134
+ uniformSurfaceClipCenterLocation,
25135
+ clipCenter.x,
25136
+ clipCenter.y,
25137
+ clipCenter.z,
25138
+ clipCenter.w
25139
+ );
25140
+ const clipBasisEast = enabled && inputs ? inputs.clipBasisEast : { x: 0, y: 0, z: 0, w: 0 };
25141
+ glContext.uniform4f(
25142
+ uniformSurfaceClipBasisEastLocation,
25143
+ clipBasisEast.x,
25144
+ clipBasisEast.y,
25145
+ clipBasisEast.z,
25146
+ clipBasisEast.w
25147
+ );
25148
+ const clipBasisNorth = enabled && inputs ? inputs.clipBasisNorth : { x: 0, y: 0, z: 0, w: 0 };
25149
+ glContext.uniform4f(
25150
+ uniformSurfaceClipBasisNorthLocation,
25151
+ clipBasisNorth.x,
25152
+ clipBasisNorth.y,
25153
+ clipBasisNorth.z,
25154
+ clipBasisNorth.w
25155
+ );
25156
+ };
25157
+ let drawOrderCounter = 0;
23500
25158
  const drawSpriteImage = (spriteEntry, imageEntry, imageResource, originCenterCache2) => {
23501
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
25159
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
23502
25160
  let screenCornerBuffer = null;
23503
25161
  const anchor = (_a2 = imageEntry.anchor) != null ? _a2 : DEFAULT_ANCHOR;
23504
25162
  const offsetDef = (_b = imageEntry.offset) != null ? _b : DEFAULT_IMAGE_OFFSET;
23505
- const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normaliseAngleDeg(
25163
+ applySurfaceMode(false);
25164
+ applySurfaceClipUniforms(false, null);
25165
+ const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normalizeAngleDeg(
23506
25166
  ((_c = imageEntry.resolvedBaseRotateDeg) != null ? _c : 0) + ((_d = imageEntry.rotateDeg) != null ? _d : 0)
23507
25167
  );
23508
25168
  const projected = mapInstance.project(spriteEntry.currentLocation);
23509
25169
  if (!projected) {
23510
25170
  return;
23511
25171
  }
25172
+ const spriteMercator = resolveSpriteMercator(spriteEntry);
23512
25173
  const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
23513
25174
  zoom,
23514
25175
  spriteEntry.currentLocation.lat
@@ -23518,7 +25179,8 @@ const createSpriteLayer = (options) => {
23518
25179
  }
23519
25180
  const perspectiveRatio = calculatePerspectiveRatio(
23520
25181
  mapInstance,
23521
- spriteEntry.currentLocation
25182
+ spriteEntry.currentLocation,
25183
+ spriteMercator
23522
25184
  );
23523
25185
  const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
23524
25186
  metersPerPixelAtLat,
@@ -23528,6 +25190,7 @@ const createSpriteLayer = (options) => {
23528
25190
  return;
23529
25191
  }
23530
25192
  const imageScale = (_e = imageEntry.scale) != null ? _e : 1;
25193
+ const altitudeMeters = (_f = spriteEntry.currentLocation.z) != null ? _f : 0;
23531
25194
  const centerParams = {
23532
25195
  mapInstance,
23533
25196
  images,
@@ -23542,7 +25205,7 @@ const createSpriteLayer = (options) => {
23542
25205
  drawingBufferHeight,
23543
25206
  pixelRatio,
23544
25207
  clipContext,
23545
- altitudeMeters: (_f = spriteEntry.currentLocation.z) != null ? _f : 0
25208
+ altitudeMeters
23546
25209
  };
23547
25210
  let baseProjected = { x: projected.x, y: projected.y };
23548
25211
  if (imageEntry.originLocation !== void 0) {
@@ -23559,6 +25222,13 @@ const createSpriteLayer = (options) => {
23559
25222
  }
23560
25223
  }
23561
25224
  if (imageEntry.mode === "surface") {
25225
+ applyScreenToClipUniforms(
25226
+ identityScaleX,
25227
+ identityScaleY,
25228
+ identityOffsetX,
25229
+ identityOffsetY
25230
+ );
25231
+ imageEntry.surfaceShaderInputs = void 0;
23562
25232
  const baseLngLat = imageEntry.originLocation !== void 0 ? (
23563
25233
  // When an origin reference is set, reproject the cached screen point back to geographic space.
23564
25234
  mapInstance.unproject([
@@ -23586,7 +25256,7 @@ const createSpriteLayer = (options) => {
23586
25256
  drawingBufferWidth,
23587
25257
  drawingBufferHeight,
23588
25258
  pixelRatio,
23589
- altitudeMeters: (_i = spriteEntry.currentLocation.z) != null ? _i : 0,
25259
+ altitudeMeters,
23590
25260
  project: !clipContext ? (lngLat) => {
23591
25261
  const result = mapInstance.project(lngLat);
23592
25262
  return result ? { x: result.x, y: result.y } : null;
@@ -23595,6 +25265,9 @@ const createSpriteLayer = (options) => {
23595
25265
  if (!surfaceCenter.center) {
23596
25266
  return;
23597
25267
  }
25268
+ if (uniformBillboardModeLocation) {
25269
+ glContext.uniform1f(uniformBillboardModeLocation, 0);
25270
+ }
23598
25271
  const offsetMeters = calculateSurfaceOffsetMeters(
23599
25272
  offsetDef,
23600
25273
  imageScale,
@@ -23608,6 +25281,51 @@ const createSpriteLayer = (options) => {
23608
25281
  totalRotateDeg,
23609
25282
  offsetMeters
23610
25283
  });
25284
+ const orderIndex = Math.min(imageEntry.order, ORDER_MAX - 1);
25285
+ const depthBiasNdc = -((imageEntry.subLayer * ORDER_BUCKET + orderIndex) * EPS_NDC);
25286
+ const displacedCenterLngLat = (_i = surfaceCenter.displacedLngLat) != null ? _i : baseLngLat;
25287
+ const displacedCenter = {
25288
+ lng: displacedCenterLngLat.lng,
25289
+ lat: displacedCenterLngLat.lat,
25290
+ z: altitudeMeters
25291
+ };
25292
+ const surfaceShaderInputs = prepareSurfaceShaderInputs({
25293
+ baseLngLat,
25294
+ worldWidthMeters: surfaceCenter.worldDimensions.width,
25295
+ worldHeightMeters: surfaceCenter.worldDimensions.height,
25296
+ anchor,
25297
+ totalRotateDeg,
25298
+ offsetMeters,
25299
+ displacedCenter,
25300
+ altitudeMeters,
25301
+ depthBiasNdc,
25302
+ scaleAdjustment: surfaceCenter.worldDimensions.scaleAdjustment,
25303
+ centerDisplacement: surfaceCenter.totalDisplacement
25304
+ });
25305
+ imageEntry.surfaceShaderInputs = surfaceShaderInputs;
25306
+ let useShaderSurface = !!clipContext;
25307
+ let clipCornerPositions = null;
25308
+ let clipCenterPosition = null;
25309
+ if (useShaderSurface) {
25310
+ clipCornerPositions = new Array(SURFACE_BASE_CORNERS.length);
25311
+ clipCenterPosition = projectLngLatToClipSpace(
25312
+ displacedCenter.lng,
25313
+ displacedCenter.lat,
25314
+ (_j = displacedCenter.z) != null ? _j : altitudeMeters,
25315
+ clipContext
25316
+ );
25317
+ if (!clipCenterPosition) {
25318
+ useShaderSurface = false;
25319
+ clipCornerPositions = null;
25320
+ }
25321
+ }
25322
+ applySurfaceMode(useShaderSurface);
25323
+ if (useShaderSurface && uniformSurfaceDepthBiasLocation) {
25324
+ glContext.uniform1f(
25325
+ uniformSurfaceDepthBiasLocation,
25326
+ surfaceShaderInputs.depthBiasNdc
25327
+ );
25328
+ }
23611
25329
  const hitTestCorners = ensureHitTestCorners(imageEntry);
23612
25330
  let bufferOffset = 0;
23613
25331
  for (const index of TRIANGLE_INDICES) {
@@ -23621,46 +25339,145 @@ const createSpriteLayer = (options) => {
23621
25339
  const clipPosition = projectLngLatToClipSpace(
23622
25340
  displaced.lng,
23623
25341
  displaced.lat,
23624
- // Default altitude to zero when sprites lack explicit elevation.
23625
- (_j = spriteEntry.currentLocation.z) != null ? _j : 0,
25342
+ altitudeMeters,
23626
25343
  clipContext
23627
25344
  );
23628
25345
  if (!clipPosition) {
23629
25346
  return;
23630
25347
  }
23631
- const screenCorner = clipToScreen(
23632
- clipPosition,
23633
- drawingBufferWidth,
23634
- drawingBufferHeight,
23635
- pixelRatio
23636
- );
23637
- if (!screenCorner) {
23638
- return;
23639
- }
23640
- const targetCorner = hitTestCorners[index];
23641
- targetCorner.x = screenCorner.x;
23642
- targetCorner.y = screenCorner.y;
23643
25348
  let [clipX, clipY, clipZ, clipW] = clipPosition;
23644
- {
23645
- const orderIndex = Math.min(imageEntry.order, ORDER_MAX - 1);
23646
- const biasIndex = imageEntry.subLayer * ORDER_BUCKET + orderIndex;
23647
- const biasNdc = -(biasIndex * EPS_NDC);
23648
- clipZ += biasNdc * clipW;
25349
+ if (!useShaderSurface) {
25350
+ const screenCorner = clipToScreen(
25351
+ clipPosition,
25352
+ drawingBufferWidth,
25353
+ drawingBufferHeight,
25354
+ pixelRatio
25355
+ );
25356
+ if (!screenCorner) {
25357
+ return;
25358
+ }
25359
+ const targetCorner = hitTestCorners[index];
25360
+ targetCorner.x = screenCorner.x;
25361
+ targetCorner.y = screenCorner.y;
25362
+ }
25363
+ if (depthBiasNdc !== 0) {
25364
+ clipZ += depthBiasNdc * clipW;
23649
25365
  const minClipZ = -clipW + MIN_CLIP_Z_EPSILON;
23650
25366
  if (clipZ < minClipZ) {
23651
25367
  clipZ = minClipZ;
23652
25368
  }
23653
25369
  }
25370
+ if (clipCornerPositions) {
25371
+ clipCornerPositions[index] = [clipX, clipY, clipZ, clipW];
25372
+ }
23654
25373
  const [u, v] = UV_CORNERS[index];
23655
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipX;
23656
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipY;
23657
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipZ;
23658
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipW;
25374
+ if (useShaderSurface) {
25375
+ const baseCorner = SURFACE_BASE_CORNERS[index];
25376
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = baseCorner[0];
25377
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = baseCorner[1];
25378
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = 0;
25379
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = 1;
25380
+ } else {
25381
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = clipX;
25382
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = clipY;
25383
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = clipZ;
25384
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = clipW;
25385
+ }
23659
25386
  QUAD_VERTEX_SCRATCH[bufferOffset++] = u;
23660
25387
  QUAD_VERTEX_SCRATCH[bufferOffset++] = v;
23661
25388
  }
25389
+ let clipUniformEnabled = false;
25390
+ if (clipCornerPositions && clipCenterPosition && clipCornerPositions.every((corner) => Array.isArray(corner))) {
25391
+ const leftTop = clipCornerPositions[0];
25392
+ const rightTop = clipCornerPositions[1];
25393
+ const leftBottom = clipCornerPositions[2];
25394
+ const rightBottom = clipCornerPositions[3];
25395
+ if (leftTop && rightTop && leftBottom && rightBottom) {
25396
+ const clipBasisEast = [
25397
+ (rightTop[0] - leftTop[0]) * 0.5,
25398
+ (rightTop[1] - leftTop[1]) * 0.5,
25399
+ (rightTop[2] - leftTop[2]) * 0.5,
25400
+ (rightTop[3] - leftTop[3]) * 0.5
25401
+ ];
25402
+ const clipBasisNorth = [
25403
+ (leftTop[0] - leftBottom[0]) * 0.5,
25404
+ (leftTop[1] - leftBottom[1]) * 0.5,
25405
+ (leftTop[2] - leftBottom[2]) * 0.5,
25406
+ (leftTop[3] - leftBottom[3]) * 0.5
25407
+ ];
25408
+ surfaceShaderInputs.clipCenter = {
25409
+ x: clipCenterPosition[0],
25410
+ y: clipCenterPosition[1],
25411
+ z: clipCenterPosition[2],
25412
+ w: clipCenterPosition[3]
25413
+ };
25414
+ surfaceShaderInputs.clipBasisEast = {
25415
+ x: clipBasisEast[0],
25416
+ y: clipBasisEast[1],
25417
+ z: clipBasisEast[2],
25418
+ w: clipBasisEast[3]
25419
+ };
25420
+ surfaceShaderInputs.clipBasisNorth = {
25421
+ x: clipBasisNorth[0],
25422
+ y: clipBasisNorth[1],
25423
+ z: clipBasisNorth[2],
25424
+ w: clipBasisNorth[3]
25425
+ };
25426
+ const clipCornersForInputs = [];
25427
+ let allCornersResolved = true;
25428
+ for (let cornerIndex = 0; cornerIndex < SURFACE_BASE_CORNERS.length; cornerIndex++) {
25429
+ const clipCorner = clipCornerPositions[cornerIndex];
25430
+ if (!clipCorner) {
25431
+ allCornersResolved = false;
25432
+ break;
25433
+ }
25434
+ clipCornersForInputs.push({
25435
+ x: clipCorner[0],
25436
+ y: clipCorner[1],
25437
+ z: clipCorner[2],
25438
+ w: clipCorner[3]
25439
+ });
25440
+ const screenCorner = clipToScreen(
25441
+ clipCorner,
25442
+ drawingBufferWidth,
25443
+ drawingBufferHeight,
25444
+ pixelRatio
25445
+ );
25446
+ if (!screenCorner) {
25447
+ return;
25448
+ }
25449
+ const targetCorner = hitTestCorners[cornerIndex];
25450
+ targetCorner.x = screenCorner.x;
25451
+ targetCorner.y = screenCorner.y;
25452
+ }
25453
+ if (allCornersResolved) {
25454
+ surfaceShaderInputs.clipCorners = clipCornersForInputs;
25455
+ clipUniformEnabled = true;
25456
+ } else {
25457
+ surfaceShaderInputs.clipCorners = [];
25458
+ }
25459
+ }
25460
+ } else {
25461
+ surfaceShaderInputs.clipCorners = [];
25462
+ }
25463
+ if (useShaderSurface) {
25464
+ applySurfaceClipUniforms(
25465
+ clipUniformEnabled,
25466
+ clipUniformEnabled ? surfaceShaderInputs : null
25467
+ );
25468
+ } else {
25469
+ applySurfaceClipUniforms(false, null);
25470
+ }
23662
25471
  screenCornerBuffer = hitTestCorners;
23663
25472
  } else {
25473
+ applyScreenToClipUniforms(
25474
+ screenToClipScaleX,
25475
+ screenToClipScaleY,
25476
+ screenToClipOffsetX,
25477
+ screenToClipOffsetY
25478
+ );
25479
+ imageEntry.surfaceShaderInputs = void 0;
25480
+ applySurfaceMode(false);
23664
25481
  const placement = calculateBillboardCenterPosition({
23665
25482
  base: baseProjected,
23666
25483
  imageWidth: imageResource.width,
@@ -23675,46 +25492,95 @@ const createSpriteLayer = (options) => {
23675
25492
  anchor,
23676
25493
  offset: offsetDef
23677
25494
  });
23678
- const corners = calculateBillboardCornerScreenPositions({
25495
+ const billboardShaderInputs = {
23679
25496
  centerX: placement.centerX,
23680
25497
  centerY: placement.centerY,
23681
25498
  halfWidth: placement.halfWidth,
23682
25499
  halfHeight: placement.halfHeight,
23683
25500
  anchor,
23684
25501
  totalRotateDeg
23685
- });
23686
- const hitTestCorners = ensureHitTestCorners(imageEntry);
23687
- let bufferOffset = 0;
23688
- for (const index of TRIANGLE_INDICES) {
23689
- const corner = corners[index];
23690
- const [clipX, clipY] = screenToClip(
23691
- corner.x,
23692
- corner.y,
23693
- drawingBufferWidth,
23694
- drawingBufferHeight,
23695
- pixelRatio
25502
+ };
25503
+ if (uniformBillboardModeLocation) {
25504
+ glContext.uniform1f(
25505
+ uniformBillboardModeLocation,
25506
+ 1
23696
25507
  );
23697
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipX;
23698
- QUAD_VERTEX_SCRATCH[bufferOffset++] = clipY;
23699
- QUAD_VERTEX_SCRATCH[bufferOffset++] = 0;
23700
- QUAD_VERTEX_SCRATCH[bufferOffset++] = 1;
23701
- QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.u;
23702
- QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.v;
23703
- }
23704
- for (let i = 0; i < corners.length; i++) {
23705
- const source = corners[i];
23706
- const target = hitTestCorners[i];
23707
- target.x = source.x;
23708
- target.y = source.y;
23709
25508
  }
23710
- screenCornerBuffer = hitTestCorners;
25509
+ const writeBillboardCorners = (corners, useShaderGeometry) => {
25510
+ const hitTestCorners = ensureHitTestCorners(imageEntry);
25511
+ let bufferOffset = 0;
25512
+ for (const index of TRIANGLE_INDICES) {
25513
+ const corner = corners[index];
25514
+ {
25515
+ const baseCorner = BILLBOARD_BASE_CORNERS[index];
25516
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = baseCorner[0];
25517
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = baseCorner[1];
25518
+ }
25519
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = 0;
25520
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = 1;
25521
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.u;
25522
+ QUAD_VERTEX_SCRATCH[bufferOffset++] = corner.v;
25523
+ }
25524
+ for (let i = 0; i < corners.length; i++) {
25525
+ const source = corners[i];
25526
+ const target = hitTestCorners[i];
25527
+ target.x = source.x;
25528
+ target.y = source.y;
25529
+ }
25530
+ screenCornerBuffer = hitTestCorners;
25531
+ };
25532
+ let resolvedCorners;
25533
+ let shaderModelCorners;
25534
+ {
25535
+ if (uniformBillboardCenterLocation) {
25536
+ glContext.uniform2f(
25537
+ uniformBillboardCenterLocation,
25538
+ billboardShaderInputs.centerX,
25539
+ billboardShaderInputs.centerY
25540
+ );
25541
+ }
25542
+ if (uniformBillboardHalfSizeLocation) {
25543
+ glContext.uniform2f(
25544
+ uniformBillboardHalfSizeLocation,
25545
+ billboardShaderInputs.halfWidth,
25546
+ billboardShaderInputs.halfHeight
25547
+ );
25548
+ }
25549
+ if (uniformBillboardAnchorLocation) {
25550
+ glContext.uniform2f(
25551
+ uniformBillboardAnchorLocation,
25552
+ (_l = (_k = billboardShaderInputs.anchor) == null ? void 0 : _k.x) != null ? _l : 0,
25553
+ (_n = (_m = billboardShaderInputs.anchor) == null ? void 0 : _m.y) != null ? _n : 0
25554
+ );
25555
+ }
25556
+ if (uniformBillboardSinCosLocation) {
25557
+ const rad = -billboardShaderInputs.totalRotateDeg * DEG2RAD;
25558
+ glContext.uniform2f(
25559
+ uniformBillboardSinCosLocation,
25560
+ Math.sin(rad),
25561
+ Math.cos(rad)
25562
+ );
25563
+ }
25564
+ shaderModelCorners = computeBillboardCornersShaderModel({
25565
+ centerX: billboardShaderInputs.centerX,
25566
+ centerY: billboardShaderInputs.centerY,
25567
+ halfWidth: billboardShaderInputs.halfWidth,
25568
+ halfHeight: billboardShaderInputs.halfHeight,
25569
+ anchor: billboardShaderInputs.anchor,
25570
+ rotationDeg: billboardShaderInputs.totalRotateDeg
25571
+ });
25572
+ resolvedCorners = shaderModelCorners;
25573
+ }
25574
+ writeBillboardCorners(resolvedCorners);
23711
25575
  }
23712
25576
  if (screenCornerBuffer && screenCornerBuffer.length === 4) {
23713
25577
  registerHitTestEntry(
23714
25578
  spriteEntry,
23715
25579
  imageEntry,
23716
- screenCornerBuffer
25580
+ screenCornerBuffer,
25581
+ drawOrderCounter
23717
25582
  );
25583
+ drawOrderCounter += 1;
23718
25584
  }
23719
25585
  glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, QUAD_VERTEX_SCRATCH);
23720
25586
  glContext.uniform1f(uniformOpacityLocation, imageEntry.opacity);
@@ -23744,6 +25610,7 @@ const createSpriteLayer = (options) => {
23744
25610
  if (!projected) {
23745
25611
  continue;
23746
25612
  }
25613
+ const spriteMercator = resolveSpriteMercator(spriteEntry);
23747
25614
  const metersPerPixelAtLat = calculateMetersPerPixelAtLatitude(
23748
25615
  zoom,
23749
25616
  spriteEntry.currentLocation.lat
@@ -23753,7 +25620,8 @@ const createSpriteLayer = (options) => {
23753
25620
  }
23754
25621
  const perspectiveRatio = calculatePerspectiveRatio(
23755
25622
  mapInstance,
23756
- spriteEntry.currentLocation
25623
+ spriteEntry.currentLocation,
25624
+ spriteMercator
23757
25625
  );
23758
25626
  const effectivePixelsPerMeter = calculateEffectivePixelsPerMeter(
23759
25627
  metersPerPixelAtLat,
@@ -23801,7 +25669,7 @@ const createSpriteLayer = (options) => {
23801
25669
  spriteMaxPixel
23802
25670
  }
23803
25671
  );
23804
- const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normaliseAngleDeg(
25672
+ const totalRotateDeg = Number.isFinite(imageEntry.displayedRotateDeg) ? imageEntry.displayedRotateDeg : normalizeAngleDeg(
23805
25673
  ((_e = imageEntry.resolvedBaseRotateDeg) != null ? _e : 0) + ((_f = imageEntry.rotateDeg) != null ? _f : 0)
23806
25674
  );
23807
25675
  const offsetMeters = calculateSurfaceOffsetMeters(
@@ -23916,13 +25784,83 @@ const createSpriteLayer = (options) => {
23916
25784
  for (const [, bucket] of sortedSubLayerBuckets) {
23917
25785
  renderSortedBucket(bucket);
23918
25786
  }
25787
+ if (showDebugBounds && debugProgram && debugVertexBuffer && debugUniformColorLocation && debugAttribPositionLocation !== -1) {
25788
+ glContext.useProgram(debugProgram);
25789
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, debugVertexBuffer);
25790
+ glContext.enableVertexAttribArray(debugAttribPositionLocation);
25791
+ glContext.vertexAttribPointer(
25792
+ debugAttribPositionLocation,
25793
+ DEBUG_OUTLINE_POSITION_COMPONENT_COUNT,
25794
+ glContext.FLOAT,
25795
+ false,
25796
+ DEBUG_OUTLINE_VERTEX_STRIDE,
25797
+ 0
25798
+ );
25799
+ glContext.disable(glContext.DEPTH_TEST);
25800
+ glContext.depthMask(false);
25801
+ glContext.uniform4f(
25802
+ debugUniformColorLocation,
25803
+ DEBUG_OUTLINE_COLOR[0],
25804
+ DEBUG_OUTLINE_COLOR[1],
25805
+ DEBUG_OUTLINE_COLOR[2],
25806
+ DEBUG_OUTLINE_COLOR[3]
25807
+ );
25808
+ if (debugUniformScreenToClipScaleLocation && debugUniformScreenToClipOffsetLocation) {
25809
+ glContext.uniform2f(
25810
+ debugUniformScreenToClipScaleLocation,
25811
+ screenToClipScaleX,
25812
+ screenToClipScaleY
25813
+ );
25814
+ glContext.uniform2f(
25815
+ debugUniformScreenToClipOffsetLocation,
25816
+ screenToClipOffsetX,
25817
+ screenToClipOffsetY
25818
+ );
25819
+ }
25820
+ for (const entry of hitTestEntries) {
25821
+ let writeOffset = 0;
25822
+ for (const cornerIndex of DEBUG_OUTLINE_CORNER_ORDER) {
25823
+ const corner = entry.corners[cornerIndex];
25824
+ DEBUG_OUTLINE_VERTEX_SCRATCH[writeOffset++] = corner.x;
25825
+ DEBUG_OUTLINE_VERTEX_SCRATCH[writeOffset++] = corner.y;
25826
+ DEBUG_OUTLINE_VERTEX_SCRATCH[writeOffset++] = 0;
25827
+ DEBUG_OUTLINE_VERTEX_SCRATCH[writeOffset++] = 1;
25828
+ }
25829
+ glContext.bufferSubData(
25830
+ glContext.ARRAY_BUFFER,
25831
+ 0,
25832
+ DEBUG_OUTLINE_VERTEX_SCRATCH
25833
+ );
25834
+ glContext.drawArrays(
25835
+ glContext.LINE_LOOP,
25836
+ 0,
25837
+ DEBUG_OUTLINE_VERTEX_COUNT
25838
+ );
25839
+ }
25840
+ glContext.depthMask(true);
25841
+ glContext.enable(glContext.DEPTH_TEST);
25842
+ glContext.disableVertexAttribArray(debugAttribPositionLocation);
25843
+ glContext.bindBuffer(glContext.ARRAY_BUFFER, null);
25844
+ }
23919
25845
  glContext.depthMask(true);
23920
25846
  glContext.enable(glContext.DEPTH_TEST);
23921
25847
  glContext.disable(glContext.BLEND);
23922
25848
  scheduleRender();
23923
25849
  };
23924
- const registerImage = async (imageId, imageSource) => {
23925
- const bitmap = typeof imageSource === "string" ? await loadImageBitmap(imageSource) : imageSource;
25850
+ const registerImage = async (imageId, imageSource, options2) => {
25851
+ let bitmap;
25852
+ try {
25853
+ bitmap = typeof imageSource === "string" ? await loadImageBitmap(imageSource, options2) : imageSource;
25854
+ } catch (error) {
25855
+ if (error instanceof SvgSizeResolutionError) {
25856
+ console.warn(
25857
+ `[SpriteLayer] Unable to register image "${imageId}": ${error.message}`,
25858
+ error
25859
+ );
25860
+ return false;
25861
+ }
25862
+ throw error;
25863
+ }
23926
25864
  if (images.has(imageId)) {
23927
25865
  return false;
23928
25866
  }
@@ -24139,6 +26077,15 @@ const createSpriteLayer = (options) => {
24139
26077
  if (!image) {
24140
26078
  return false;
24141
26079
  }
26080
+ sprites.forEach((sprite) => {
26081
+ sprite.images.forEach((orderMap) => {
26082
+ orderMap.forEach((imageState) => {
26083
+ if (imageState.imageId === imageId) {
26084
+ removeImageBoundsFromHitTestTree(imageState);
26085
+ }
26086
+ });
26087
+ });
26088
+ });
24142
26089
  const glContext = gl;
24143
26090
  if (glContext && image.texture) {
24144
26091
  glContext.deleteTexture(image.texture);
@@ -24161,13 +26108,16 @@ const createSpriteLayer = (options) => {
24161
26108
  }
24162
26109
  });
24163
26110
  images.clear();
26111
+ hitTestTree.clear();
26112
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
26113
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24164
26114
  clearTextureQueue();
24165
26115
  ensureRenderTargetEntries();
24166
26116
  scheduleRender();
24167
26117
  };
24168
26118
  const getAllImageIds = () => Array.from(images.keys());
24169
26119
  const addSpriteInternal = (spriteId, init) => {
24170
- var _a2, _b, _c, _d;
26120
+ var _a2, _b, _c, _d, _e;
24171
26121
  if (sprites.get(spriteId)) {
24172
26122
  return false;
24173
26123
  }
@@ -24228,23 +26178,33 @@ const createSpriteLayer = (options) => {
24228
26178
  }
24229
26179
  }
24230
26180
  const currentLocation = cloneSpriteLocation(init.location);
26181
+ const initialAltitude = (_c = currentLocation.z) != null ? _c : 0;
26182
+ const initialMercator = maplibreGlExports.MercatorCoordinate.fromLngLat(
26183
+ { lng: currentLocation.lng, lat: currentLocation.lat },
26184
+ initialAltitude
26185
+ );
24231
26186
  const spriteState = {
24232
26187
  spriteId,
24233
26188
  // Sprites default to enabled unless explicitly disabled in the init payload.
24234
- isEnabled: (_c = init.isEnabled) != null ? _c : true,
26189
+ isEnabled: (_d = init.isEnabled) != null ? _d : true,
24235
26190
  currentLocation,
24236
26191
  fromLocation: void 0,
24237
26192
  toLocation: void 0,
24238
26193
  images: images2,
24239
26194
  // Tags default to null to simplify downstream comparisons.
24240
- tag: (_d = init.tag) != null ? _d : null,
26195
+ tag: (_e = init.tag) != null ? _e : null,
24241
26196
  interpolationState: null,
24242
26197
  pendingInterpolationOptions: null,
24243
26198
  lastCommandLocation: cloneSpriteLocation(currentLocation),
24244
26199
  lastAutoRotationLocation: cloneSpriteLocation(currentLocation),
24245
- lastAutoRotationAngleDeg: 0
26200
+ lastAutoRotationAngleDeg: 0,
26201
+ cachedMercator: initialMercator,
26202
+ cachedMercatorLng: currentLocation.lng,
26203
+ cachedMercatorLat: currentLocation.lat,
26204
+ cachedMercatorZ: initialAltitude
24246
26205
  };
24247
26206
  sprites.set(spriteId, spriteState);
26207
+ refreshSpriteHitTestBounds(spriteState);
24248
26208
  return true;
24249
26209
  };
24250
26210
  const resolveSpriteInitCollection = (collection) => {
@@ -24279,7 +26239,19 @@ const createSpriteLayer = (options) => {
24279
26239
  }
24280
26240
  return addedCount;
24281
26241
  };
24282
- const removeSpriteInternal = (spriteId) => sprites.delete(spriteId);
26242
+ const removeSpriteInternal = (spriteId) => {
26243
+ const sprite = sprites.get(spriteId);
26244
+ if (!sprite) {
26245
+ return false;
26246
+ }
26247
+ sprite.images.forEach((orderMap) => {
26248
+ orderMap.forEach((image) => {
26249
+ removeImageBoundsFromHitTestTree(image);
26250
+ });
26251
+ });
26252
+ sprites.delete(spriteId);
26253
+ return true;
26254
+ };
24283
26255
  const removeSprite = (spriteId) => {
24284
26256
  const removed = removeSpriteInternal(spriteId);
24285
26257
  if (!removed) {
@@ -24307,6 +26279,9 @@ const createSpriteLayer = (options) => {
24307
26279
  if (removedCount === 0) {
24308
26280
  return 0;
24309
26281
  }
26282
+ hitTestTree.clear();
26283
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
26284
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
24310
26285
  sprites.clear();
24311
26286
  ensureRenderTargetEntries();
24312
26287
  scheduleRender();
@@ -24323,6 +26298,9 @@ const createSpriteLayer = (options) => {
24323
26298
  let removedCount = 0;
24324
26299
  sprite.images.forEach((orderMap) => {
24325
26300
  removedCount += orderMap.size;
26301
+ orderMap.forEach((image) => {
26302
+ removeImageBoundsFromHitTestTree(image);
26303
+ });
24326
26304
  });
24327
26305
  sprite.images.clear();
24328
26306
  ensureRenderTargetEntries();
@@ -24371,8 +26349,9 @@ const createSpriteLayer = (options) => {
24371
26349
  if (state.autoRotation) {
24372
26350
  state.resolvedBaseRotateDeg = sprite.lastAutoRotationAngleDeg;
24373
26351
  }
24374
- updateImageDisplayedRotation(state);
26352
+ syncImageRotationChannel(state);
24375
26353
  setImageState(sprite, state);
26354
+ registerImageBoundsInHitTestTree(sprite, state);
24376
26355
  resultOut.isUpdated = true;
24377
26356
  return true;
24378
26357
  };
@@ -24412,41 +26391,32 @@ const createSpriteLayer = (options) => {
24412
26391
  if (imageUpdate.anchor !== void 0) {
24413
26392
  state.anchor = cloneAnchor(imageUpdate.anchor);
24414
26393
  }
24415
- const rotationInterpolation = imageUpdate.rotationInterpolation;
24416
- const offsetInterpolationOption = rotationInterpolation == null ? void 0 : rotationInterpolation.offsetDeg;
24417
- const rotateInterpolationOption = rotationInterpolation == null ? void 0 : rotationInterpolation.rotateDeg;
26394
+ const interpolationOptions = imageUpdate.interpolation;
26395
+ const offsetDegInterpolationOption = interpolationOptions == null ? void 0 : interpolationOptions.offsetDeg;
26396
+ const offsetMetersInterpolationOption = interpolationOptions == null ? void 0 : interpolationOptions.offsetMeters;
26397
+ const rotateInterpolationOption = interpolationOptions == null ? void 0 : interpolationOptions.rotateDeg;
24418
26398
  let rotationOverride;
24419
26399
  let hasRotationOverride = false;
24420
26400
  if (imageUpdate.offset !== void 0) {
24421
- const newOffset = cloneOffset(imageUpdate.offset);
24422
- if (offsetInterpolationOption && offsetInterpolationOption.durationMs > 0) {
24423
- const { state: interpolationState, requiresInterpolation } = createNumericInterpolationState({
24424
- currentValue: state.offset.offsetDeg,
24425
- targetValue: newOffset.offsetDeg,
24426
- options: offsetInterpolationOption
24427
- });
24428
- if (requiresInterpolation) {
24429
- state.offset.offsetMeters = newOffset.offsetMeters;
24430
- state.offsetInterpolationState = interpolationState;
24431
- } else {
24432
- state.offset = newOffset;
24433
- state.offsetInterpolationState = null;
24434
- }
24435
- } else {
24436
- state.offset = newOffset;
24437
- state.offsetInterpolationState = null;
26401
+ const clonedOffset = cloneOffset(imageUpdate.offset);
26402
+ applyOffsetUpdate(state, clonedOffset, {
26403
+ deg: offsetDegInterpolationOption,
26404
+ meters: offsetMetersInterpolationOption
26405
+ });
26406
+ } else {
26407
+ if (offsetDegInterpolationOption === null) {
26408
+ clearOffsetDegInterpolation(state);
26409
+ }
26410
+ if (offsetMetersInterpolationOption === null) {
26411
+ clearOffsetMetersInterpolation(state);
24438
26412
  }
24439
- } else if (offsetInterpolationOption === null) {
24440
- state.offsetInterpolationState = null;
24441
26413
  }
24442
26414
  if (rotateInterpolationOption !== void 0) {
24443
26415
  if (rotateInterpolationOption === null) {
24444
26416
  state.rotationInterpolationOptions = null;
24445
26417
  rotationOverride = null;
24446
26418
  } else {
24447
- const cloned = cloneNumericInterpolationOptions(
24448
- rotateInterpolationOption
24449
- );
26419
+ const cloned = cloneInterpolationOptions(rotateInterpolationOption);
24450
26420
  state.rotationInterpolationOptions = cloned;
24451
26421
  rotationOverride = cloned;
24452
26422
  }
@@ -24488,12 +26458,13 @@ const createSpriteLayer = (options) => {
24488
26458
  }
24489
26459
  }
24490
26460
  if (requireRotationSync) {
24491
- updateImageDisplayedRotation(
26461
+ syncImageRotationChannel(
24492
26462
  state,
24493
26463
  // When a rotation override has been computed, pass it along (null clears interpolation); otherwise leave undefined.
24494
26464
  hasRotationOverride ? rotationOverride != null ? rotationOverride : null : void 0
24495
26465
  );
24496
26466
  }
26467
+ registerImageBoundsInHitTestTree(sprite, state);
24497
26468
  resultOut.isUpdated = true;
24498
26469
  return true;
24499
26470
  };
@@ -24512,6 +26483,10 @@ const createSpriteLayer = (options) => {
24512
26483
  return true;
24513
26484
  };
24514
26485
  const removeSpriteImageInternal = (sprite, subLayer, order, resultOut) => {
26486
+ const state = getImageState(sprite, subLayer, order);
26487
+ if (state) {
26488
+ removeImageBoundsFromHitTestTree(state);
26489
+ }
24515
26490
  const deleted = deleteImageState(sprite, subLayer, order);
24516
26491
  if (deleted) {
24517
26492
  resultOut.isUpdated = true;
@@ -24541,11 +26516,13 @@ const createSpriteLayer = (options) => {
24541
26516
  }
24542
26517
  let updated = false;
24543
26518
  let isRequiredRender = false;
26519
+ let needsHitTestRefresh = false;
24544
26520
  if (update.isEnabled !== void 0) {
24545
26521
  if (sprite.isEnabled !== update.isEnabled) {
24546
26522
  sprite.isEnabled = update.isEnabled;
24547
26523
  updated = true;
24548
26524
  isRequiredRender = true;
26525
+ needsHitTestRefresh = true;
24549
26526
  }
24550
26527
  }
24551
26528
  let interpolationOptionsForLocation = void 0;
@@ -24622,6 +26599,7 @@ const createSpriteLayer = (options) => {
24622
26599
  }
24623
26600
  sprite.pendingInterpolationOptions = null;
24624
26601
  applyAutoRotation(sprite, newCommandLocation);
26602
+ needsHitTestRefresh = true;
24625
26603
  }
24626
26604
  if (update.tag !== void 0) {
24627
26605
  const nextTag = (_a2 = update.tag) != null ? _a2 : null;
@@ -24630,6 +26608,9 @@ const createSpriteLayer = (options) => {
24630
26608
  updated = true;
24631
26609
  }
24632
26610
  }
26611
+ if (needsHitTestRefresh) {
26612
+ refreshSpriteHitTestBounds(sprite);
26613
+ }
24633
26614
  if (isRequiredRender) {
24634
26615
  return "isRequiredRender";
24635
26616
  }
@@ -24653,34 +26634,6 @@ const createSpriteLayer = (options) => {
24653
26634
  return true;
24654
26635
  }
24655
26636
  };
24656
- const updateBulk = (updateBulkList) => {
24657
- let updatedCount = 0;
24658
- let isRequiredRender = false;
24659
- updateBulkList.forEach((update) => {
24660
- const result = updateSpriteInternal(update.spriteId, update);
24661
- switch (result) {
24662
- case "notfound":
24663
- // Sprite missing; nothing to do for this entry.
24664
- case "ignored":
24665
- break;
24666
- case "updated":
24667
- updatedCount++;
24668
- break;
24669
- // When rendering must occur because of this update
24670
- case "isRequiredRender":
24671
- ensureRenderTargetEntries();
24672
- scheduleRender();
24673
- updatedCount++;
24674
- isRequiredRender = true;
24675
- break;
24676
- }
24677
- });
24678
- if (isRequiredRender) {
24679
- ensureRenderTargetEntries();
24680
- scheduleRender();
24681
- }
24682
- return updatedCount;
24683
- };
24684
26637
  const mutateSprites = (sourceItems, mutator) => {
24685
26638
  if (sourceItems.length === 0) {
24686
26639
  return 0;
@@ -24693,6 +26646,10 @@ const createSpriteLayer = (options) => {
24693
26646
  isUpdated: false
24694
26647
  };
24695
26648
  const updateObject = {
26649
+ isEnabled: void 0,
26650
+ location: void 0,
26651
+ interpolation: void 0,
26652
+ tag: void 0,
24696
26653
  getImageIndexMap: () => {
24697
26654
  const map2 = /* @__PURE__ */ new Map();
24698
26655
  currentSprite.images.forEach((inner, subLayer) => {
@@ -24886,9 +26843,23 @@ const createSpriteLayer = (options) => {
24886
26843
  updateSpriteImage,
24887
26844
  removeSpriteImage,
24888
26845
  updateSprite,
24889
- updateBulk,
24890
26846
  mutateSprites,
24891
26847
  updateForEach,
26848
+ setHitTestEnabled: (enabled) => {
26849
+ if (isHitTestEnabled === enabled) {
26850
+ return;
26851
+ }
26852
+ isHitTestEnabled = enabled;
26853
+ hitTestTree.clear();
26854
+ hitTestTreeItems = /* @__PURE__ */ new WeakMap();
26855
+ hitTestEntryByImage = /* @__PURE__ */ new WeakMap();
26856
+ if (!enabled) {
26857
+ return;
26858
+ }
26859
+ sprites.forEach((sprite) => {
26860
+ refreshSpriteHitTestBounds(sprite);
26861
+ });
26862
+ },
24892
26863
  on: addEventListener2,
24893
26864
  off: removeEventListener2
24894
26865
  };
@@ -24897,6 +26868,7 @@ const createSpriteLayer = (options) => {
24897
26868
  exports.BETTER_TEXTURE_FILTERING_OPTIONS = BETTER_TEXTURE_FILTERING_OPTIONS;
24898
26869
  exports.DEFAULT_TEXTURE_FILTERING_OPTIONS = DEFAULT_TEXTURE_FILTERING_OPTIONS;
24899
26870
  exports.STANDARD_SPRITE_SCALING_OPTIONS = STANDARD_SPRITE_SCALING_OPTIONS;
26871
+ exports.SvgSizeResolutionError = SvgSizeResolutionError;
24900
26872
  exports.UNLIMITED_SPRITE_SCALING_OPTIONS = UNLIMITED_SPRITE_SCALING_OPTIONS;
24901
26873
  exports.applyAutoRotation = applyAutoRotation;
24902
26874
  exports.calculatePerspectiveRatio = calculatePerspectiveRatio;
@@ -24907,6 +26879,8 @@ exports.compileShader = compileShader;
24907
26879
  exports.createImageStateFromInit = createImageStateFromInit;
24908
26880
  exports.createShaderProgram = createShaderProgram;
24909
26881
  exports.createSpriteLayer = createSpriteLayer;
26882
+ exports.loadImageBitmap = loadImageBitmap;
24910
26883
  exports.multiplyMatrixAndVector = multiplyMatrixAndVector;
24911
26884
  exports.projectLngLatToClipSpace = projectLngLatToClipSpace;
26885
+ exports.readImageBitmap = readImageBitmap;
24912
26886
  //# sourceMappingURL=index.cjs.map