@deck.gl/extensions 9.4.0-beta.1 → 9.4.0-beta.3

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.
Files changed (37) hide show
  1. package/dist/dist.dev.js +1281 -120
  2. package/dist/fill-style/fill-style-extension.d.ts +38 -6
  3. package/dist/fill-style/fill-style-extension.d.ts.map +1 -1
  4. package/dist/fill-style/fill-style-extension.js +88 -10
  5. package/dist/fill-style/fill-style-extension.js.map +1 -1
  6. package/dist/fill-style/procedural-pattern.d.ts +65 -0
  7. package/dist/fill-style/procedural-pattern.d.ts.map +1 -0
  8. package/dist/fill-style/procedural-pattern.js +135 -0
  9. package/dist/fill-style/procedural-pattern.js.map +1 -0
  10. package/dist/fill-style/shader-module.d.ts +13 -4
  11. package/dist/fill-style/shader-module.d.ts.map +1 -1
  12. package/dist/fill-style/shader-module.js +212 -21
  13. package/dist/fill-style/shader-module.js.map +1 -1
  14. package/dist/index.cjs +946 -172
  15. package/dist/index.cjs.map +3 -3
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/mask/shader-module.d.ts +1 -0
  19. package/dist/mask/shader-module.d.ts.map +1 -1
  20. package/dist/path-style/path-style-extension.d.ts +49 -6
  21. package/dist/path-style/path-style-extension.d.ts.map +1 -1
  22. package/dist/path-style/path-style-extension.js +237 -37
  23. package/dist/path-style/path-style-extension.js.map +1 -1
  24. package/dist/path-style/shaders.glsl.d.ts +30 -1
  25. package/dist/path-style/shaders.glsl.d.ts.map +1 -1
  26. package/dist/path-style/shaders.glsl.js +136 -21
  27. package/dist/path-style/shaders.glsl.js.map +1 -1
  28. package/dist/terrain/shader-module.d.ts +1 -0
  29. package/dist/terrain/shader-module.d.ts.map +1 -1
  30. package/dist.min.js +377 -98
  31. package/package.json +6 -6
  32. package/src/fill-style/fill-style-extension.ts +135 -13
  33. package/src/fill-style/procedural-pattern.ts +203 -0
  34. package/src/fill-style/shader-module.ts +233 -23
  35. package/src/index.ts +10 -1
  36. package/src/path-style/path-style-extension.ts +350 -44
  37. package/src/path-style/shaders.glsl.ts +268 -27
package/dist/index.cjs CHANGED
@@ -15,7 +15,7 @@ var __copyProps = (to, from, except, desc) => {
15
15
  }
16
16
  return to;
17
17
  };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
19
19
 
20
20
  // dist/index.js
21
21
  var index_exports = {};
@@ -608,7 +608,7 @@ fn project_mercator_(lnglat: vec2<f32>) -> vec2<f32> {
608
608
  let y = clamp(lnglat.y, -89.9, 89.9);
609
609
  return vec2<f32>(
610
610
  radians(x) + PI,
611
- PI + log(tan(PI * 0.25 + radians(y) * 0.5))
611
+ PI + log(tan_fp32(PI * 0.25 + radians(y) * 0.5))
612
612
  ) * WORLD_SCALE;
613
613
  }
614
614
 
@@ -1480,6 +1480,10 @@ var import_core2 = require("@math.gl/core");
1480
1480
  function createMat4() {
1481
1481
  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
1482
1482
  }
1483
+ function mod(value, divisor) {
1484
+ const modulus = value % divisor;
1485
+ return modulus < 0 ? divisor + modulus : modulus;
1486
+ }
1483
1487
  function getCameraPosition(viewMatrixInverse) {
1484
1488
  return [viewMatrixInverse[12], viewMatrixInverse[13], viewMatrixInverse[14]];
1485
1489
  }
@@ -1603,8 +1607,8 @@ var _Viewport = class _Viewport {
1603
1607
  }
1604
1608
  return PROJECTION_MODE.IDENTITY;
1605
1609
  }
1606
- // Two viewports are equal if width and height are identical, and if
1607
- // their view and projection matrices are (approximately) equal.
1610
+ // Two viewports are equal if their dimensions, common-space scale, and
1611
+ // view and projection matrices are (approximately) equal.
1608
1612
  equals(viewport) {
1609
1613
  if (!(viewport instanceof _Viewport)) {
1610
1614
  return false;
@@ -1612,7 +1616,7 @@ var _Viewport = class _Viewport {
1612
1616
  if (this === viewport) {
1613
1617
  return true;
1614
1618
  }
1615
- return viewport.width === this.width && viewport.height === this.height && viewport.scale === this.scale && (0, import_core3.equals)(viewport.projectionMatrix, this.projectionMatrix) && (0, import_core3.equals)(viewport.viewMatrix, this.viewMatrix);
1619
+ return viewport.width === this.width && viewport.height === this.height && viewport.scale === this.scale && viewport.projectionMode === this.projectionMode && viewport.resolution === this.resolution && (0, import_core3.equals)(viewport.distanceScales.unitsPerMeter, this.distanceScales.unitsPerMeter) && (0, import_core3.equals)(viewport.projectionMatrix, this.projectionMatrix) && (0, import_core3.equals)(viewport.viewMatrix, this.viewMatrix);
1616
1620
  }
1617
1621
  /**
1618
1622
  * Projects xyz (possibly latitude and longitude) to pixel coordinates in window
@@ -1949,6 +1953,10 @@ var _WebMercatorViewport = class _WebMercatorViewport extends Viewport {
1949
1953
  }
1950
1954
  return this._subViewports;
1951
1955
  }
1956
+ /** Returns whether two Web Mercator viewports use the same projection settings. */
1957
+ equals(viewport) {
1958
+ return viewport instanceof _WebMercatorViewport && viewport._pseudoMeters === this._pseudoMeters && super.equals(viewport);
1959
+ }
1952
1960
  projectPosition(xyz) {
1953
1961
  if (this._pseudoMeters) {
1954
1962
  return super.projectPosition(xyz);
@@ -2198,15 +2206,52 @@ function deepEqual(a, b, depth) {
2198
2206
  return false;
2199
2207
  }
2200
2208
 
2209
+ // ../core/src/utils/shader.ts
2210
+ function mergeShaders(target, source2) {
2211
+ if (!source2) {
2212
+ return target;
2213
+ }
2214
+ const result = { ...target, ...source2 };
2215
+ if ("defines" in source2) {
2216
+ result.defines = { ...target.defines, ...source2.defines };
2217
+ }
2218
+ if ("modules" in source2) {
2219
+ result.modules = (target.modules || []).concat(source2.modules);
2220
+ if (source2.modules.some((module2) => module2.name === "project64")) {
2221
+ const index = result.modules.findIndex((module2) => module2.name === "project32");
2222
+ if (index >= 0) {
2223
+ result.modules.splice(index, 1);
2224
+ }
2225
+ }
2226
+ }
2227
+ if ("inject" in source2) {
2228
+ if (!target.inject) {
2229
+ result.inject = source2.inject;
2230
+ } else {
2231
+ const mergedInjection = { ...target.inject };
2232
+ for (const key in source2.inject) {
2233
+ mergedInjection[key] = (mergedInjection[key] || "") + source2.inject[key];
2234
+ }
2235
+ result.inject = mergedInjection;
2236
+ }
2237
+ }
2238
+ return result;
2239
+ }
2240
+
2201
2241
  // ../core/src/viewports/globe-viewport.ts
2202
2242
  var import_core5 = require("@math.gl/core");
2203
2243
  var import_web_mercator3 = require("@math.gl/web-mercator");
2204
- var import_core6 = require("@math.gl/core");
2205
- var import_web_mercator4 = require("@math.gl/web-mercator");
2206
2244
  var DEGREES_TO_RADIANS2 = Math.PI / 180;
2207
2245
  var RADIANS_TO_DEGREES = 180 / Math.PI;
2246
+ var NORTH_UP_BEARING_THRESHOLD = 1;
2208
2247
  var EARTH_RADIUS = 6370972;
2209
2248
  var GLOBE_RADIUS = 256;
2249
+ var GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO = 0.75;
2250
+ var GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO = 1.15;
2251
+ function isGlobeNorthUp(bearing) {
2252
+ const normalizedBearing = mod(bearing + 180, 360) - 180;
2253
+ return Math.abs(normalizedBearing) < NORTH_UP_BEARING_THRESHOLD;
2254
+ }
2210
2255
  function getDistanceScales2() {
2211
2256
  const unitsPerMeter2 = GLOBE_RADIUS / EARTH_RADIUS;
2212
2257
  const unitsPerDegree = Math.PI / 180 * GLOBE_RADIUS;
@@ -2240,7 +2285,7 @@ var GlobeViewport = class extends Viewport {
2240
2285
  } else {
2241
2286
  fovy = (0, import_web_mercator3.altitudeToFovy)(altitude);
2242
2287
  }
2243
- const scaleLatitude = Math.max(Math.min(latitude, import_web_mercator4.MAX_LATITUDE), -import_web_mercator4.MAX_LATITUDE);
2288
+ const scaleLatitude = Math.max(Math.min(latitude, import_web_mercator3.MAX_LATITUDE), -import_web_mercator3.MAX_LATITUDE);
2244
2289
  const scale = Math.pow(2, zoom - zoomAdjust(scaleLatitude));
2245
2290
  const pitchRadians = pitch * DEGREES_TO_RADIANS2;
2246
2291
  const nearZ = opts.nearZ ?? nearZMultiplier;
@@ -2291,6 +2336,67 @@ var GlobeViewport = class extends Viewport {
2291
2336
  Math.max(left[1], right[1], top[1], bottom[1])
2292
2337
  ];
2293
2338
  }
2339
+ /**
2340
+ * Builds the screen-pixel → globe-center ray and the intermediate ray/sphere
2341
+ * math reused by `unproject` and anchored zoom. One function so the same
2342
+ * pixelUnprojectionMatrix work isn't duplicated.
2343
+ */
2344
+ _getRayToGlobe(screenPosition, { topLeft = true, targetZ } = {}) {
2345
+ const [screenX, screenY] = screenPosition;
2346
+ const adjustedScreenY = topLeft ? screenY : this.height - screenY;
2347
+ const { pixelUnprojectionMatrix } = this;
2348
+ const rayStartPosition = transformVector(pixelUnprojectionMatrix, [
2349
+ screenX,
2350
+ adjustedScreenY,
2351
+ -1,
2352
+ 1
2353
+ ]);
2354
+ const rayEndPosition = transformVector(pixelUnprojectionMatrix, [
2355
+ screenX,
2356
+ adjustedScreenY,
2357
+ 1,
2358
+ 1
2359
+ ]);
2360
+ const radius = ((targetZ || 0) / EARTH_RADIUS + 1) * GLOBE_RADIUS;
2361
+ const rayLengthSquared = import_core5.vec3.sqrLen(import_core5.vec3.sub([], rayStartPosition, rayEndPosition));
2362
+ const rayStartDistanceSquared = import_core5.vec3.sqrLen(rayStartPosition);
2363
+ const rayEndDistanceSquared = import_core5.vec3.sqrLen(rayEndPosition);
2364
+ const triangleAreaSquared = (4 * rayStartDistanceSquared * rayEndDistanceSquared - (rayLengthSquared - rayStartDistanceSquared - rayEndDistanceSquared) ** 2) / 16;
2365
+ const distanceToCenterSquared = 4 * triangleAreaSquared / rayLengthSquared;
2366
+ return {
2367
+ rayStartPosition,
2368
+ rayEndPosition,
2369
+ radius,
2370
+ rayLengthSquared,
2371
+ rayStartDistanceSquared,
2372
+ distanceToCenterSquared
2373
+ };
2374
+ }
2375
+ _getRayDistanceToGlobeCenterRatio(screenPosition, options) {
2376
+ const { distanceToCenterSquared, radius } = this._getRayToGlobe(screenPosition, options);
2377
+ return Math.sqrt(Math.max(0, distanceToCenterSquared)) / radius;
2378
+ }
2379
+ /**
2380
+ * Returns how strongly a screen position should anchor zoom on the visible globe.
2381
+ * A value of `0` means that the controller should fall back to center zoom.
2382
+ * @param screenPosition - Screen position to evaluate.
2383
+ * @returns Anchor strength from `0` to `1`.
2384
+ */
2385
+ getZoomAnchorStrength(screenPosition) {
2386
+ const distanceRatio = this._getRayDistanceToGlobeCenterRatio(screenPosition);
2387
+ if (distanceRatio >= GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO) {
2388
+ return 0;
2389
+ }
2390
+ const edgeProgress = Math.max(
2391
+ 0,
2392
+ Math.min(
2393
+ 1,
2394
+ (distanceRatio - GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO) / (GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO - GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO)
2395
+ )
2396
+ );
2397
+ const smoothProgress = edgeProgress * edgeProgress * (3 - 2 * edgeProgress);
2398
+ return 1 - smoothProgress;
2399
+ }
2294
2400
  unproject(xyz, { topLeft = true, targetZ } = {}) {
2295
2401
  const [x, y, z] = xyz;
2296
2402
  const y2 = topLeft ? y : this.height - y;
@@ -2299,18 +2405,22 @@ var GlobeViewport = class extends Viewport {
2299
2405
  if (Number.isFinite(z)) {
2300
2406
  coord = transformVector(pixelUnprojectionMatrix, [x, y2, z, 1]);
2301
2407
  } else {
2302
- const coord0 = transformVector(pixelUnprojectionMatrix, [x, y2, -1, 1]);
2303
- const coord1 = transformVector(pixelUnprojectionMatrix, [x, y2, 1, 1]);
2304
- const lt = ((targetZ || 0) / EARTH_RADIUS + 1) * GLOBE_RADIUS;
2305
- const lSqr = import_core6.vec3.sqrLen(import_core6.vec3.sub([], coord0, coord1));
2306
- const l0Sqr = import_core6.vec3.sqrLen(coord0);
2307
- const l1Sqr = import_core6.vec3.sqrLen(coord1);
2308
- const sSqr = (4 * l0Sqr * l1Sqr - (lSqr - l0Sqr - l1Sqr) ** 2) / 16;
2309
- const dSqr = 4 * sSqr / lSqr;
2310
- const r0 = Math.sqrt(l0Sqr - dSqr);
2311
- const dr = Math.sqrt(Math.max(0, lt * lt - dSqr));
2312
- const t = (r0 - dr) / Math.sqrt(lSqr);
2313
- coord = import_core6.vec3.lerp([], coord0, coord1, t);
2408
+ const {
2409
+ rayStartPosition,
2410
+ rayEndPosition,
2411
+ radius,
2412
+ rayLengthSquared,
2413
+ rayStartDistanceSquared,
2414
+ distanceToCenterSquared
2415
+ } = this._getRayToGlobe(xyz, { topLeft, targetZ });
2416
+ const rayStartToClosestApproach = Math.sqrt(
2417
+ rayStartDistanceSquared - distanceToCenterSquared
2418
+ );
2419
+ const closestApproachToIntersection = Math.sqrt(
2420
+ Math.max(0, radius * radius - distanceToCenterSquared)
2421
+ );
2422
+ const intersectionRatio = (rayStartToClosestApproach - closestApproachToIntersection) / Math.sqrt(rayLengthSquared);
2423
+ coord = import_core5.vec3.lerp([], rayStartPosition, rayEndPosition, intersectionRatio);
2314
2424
  }
2315
2425
  const [X, Y, Z] = this.unprojectPosition(coord);
2316
2426
  if (Number.isFinite(z)) {
@@ -2328,7 +2438,7 @@ var GlobeViewport = class extends Viewport {
2328
2438
  }
2329
2439
  unprojectPosition(xyz) {
2330
2440
  const [x, y, z] = xyz;
2331
- const D = import_core6.vec3.len(xyz);
2441
+ const D = import_core5.vec3.len(xyz);
2332
2442
  const phi = Math.asin(z / D);
2333
2443
  const lambda = Math.atan2(x, -y);
2334
2444
  const lng = lambda * RADIANS_TO_DEGREES;
@@ -2343,73 +2453,64 @@ var GlobeViewport = class extends Viewport {
2343
2453
  return xyz;
2344
2454
  }
2345
2455
  /**
2346
- * Pan the globe using delta-based movement
2347
- * @param coords - the geographic coordinates where the pan started
2348
- * @param pixel - the current screen position
2349
- * @param startPixel - the screen position where the pan started
2350
- * @returns updated viewport options with new longitude/latitude
2456
+ * Pan the globe to place geographic coordinates at a screen pixel.
2457
+ * When `dragStartPosition` is supplied, applies the delta-based movement used by globe dragging.
2458
+ * @param coordinates - Geographic anchor, or the starting longitude, latitude and zoom.
2459
+ * @param screenPosition - Current screen position.
2460
+ * @param dragStartPosition - Screen position where a drag started.
2461
+ * @returns Updated viewport options.
2351
2462
  */
2352
- panByPosition([startLng, startLat, startZoom], pixel, startPixel) {
2463
+ panByPosition(coordinates, screenPosition, dragStartPosition) {
2464
+ if (!dragStartPosition) {
2465
+ let anchorStrength = this.getZoomAnchorStrength(screenPosition);
2466
+ if (anchorStrength === 0) {
2467
+ return { longitude: this.longitude, latitude: this.latitude };
2468
+ }
2469
+ const currentCoordinates = this.unproject(screenPosition);
2470
+ const longitudeDelta = mod(coordinates[0] - currentCoordinates[0] + 180, 360) - 180;
2471
+ const latitudeDelta = coordinates[1] - currentCoordinates[1];
2472
+ const crossesPole = Math.abs(currentCoordinates[1]) > import_web_mercator3.MAX_LATITUDE || Math.abs(longitudeDelta) > 90;
2473
+ if (isGlobeNorthUp(this.bearing) && crossesPole) {
2474
+ return { longitude: this.longitude, latitude: this.latitude };
2475
+ }
2476
+ if (isGlobeNorthUp(this.bearing) && latitudeDelta !== 0) {
2477
+ const latitudeLimit = latitudeDelta > 0 ? import_web_mercator3.MAX_LATITUDE : -import_web_mercator3.MAX_LATITUDE;
2478
+ const latitudeConstraintStrength = (latitudeLimit - this.latitude) / latitudeDelta;
2479
+ anchorStrength = Math.min(anchorStrength, Math.max(0, latitudeConstraintStrength));
2480
+ }
2481
+ const longitude2 = this.longitude + longitudeDelta * anchorStrength;
2482
+ const latitude2 = Math.max(Math.min(this.latitude + latitudeDelta * anchorStrength, 90), -90);
2483
+ return { longitude: longitude2, latitude: latitude2 };
2484
+ }
2485
+ const [startLongitude, startLatitude, startZoom] = coordinates;
2353
2486
  const scale = Math.pow(2, this.zoom - zoomAdjust(this.latitude));
2354
2487
  const rotationSpeed = 0.25 / scale;
2355
- const longitude = startLng + rotationSpeed * (startPixel[0] - pixel[0]);
2356
- let latitude = startLat - rotationSpeed * (startPixel[1] - pixel[1]);
2488
+ const longitude = startLongitude + rotationSpeed * (dragStartPosition[0] - screenPosition[0]);
2489
+ let latitude = startLatitude - rotationSpeed * (dragStartPosition[1] - screenPosition[1]);
2357
2490
  latitude = Math.max(Math.min(latitude, 90), -90);
2358
- const out = { longitude, latitude, zoom: startZoom - zoomAdjust(startLat) };
2359
- out.zoom += zoomAdjust(out.latitude);
2360
- return out;
2491
+ const nextViewState = { longitude, latitude, zoom: startZoom - zoomAdjust(startLatitude) };
2492
+ nextViewState.zoom += zoomAdjust(nextViewState.latitude);
2493
+ return nextViewState;
2361
2494
  }
2362
2495
  };
2363
2496
  GlobeViewport.displayName = "GlobeViewport";
2364
2497
  function zoomAdjust(latitude, clampToPoles) {
2365
2498
  if (clampToPoles) {
2366
- latitude = Math.max(Math.min(latitude, import_web_mercator4.MAX_LATITUDE), -import_web_mercator4.MAX_LATITUDE);
2499
+ latitude = Math.max(Math.min(latitude, import_web_mercator3.MAX_LATITUDE), -import_web_mercator3.MAX_LATITUDE);
2367
2500
  }
2368
2501
  const scaleAdjust = Math.PI * Math.cos(latitude * Math.PI / 180);
2369
2502
  return Math.log2(scaleAdjust);
2370
2503
  }
2371
2504
  function transformVector(matrix, vector) {
2372
- const result = import_core6.vec4.transformMat4([], vector, matrix);
2373
- import_core6.vec4.scale(result, result, 1 / result[3]);
2374
- return result;
2375
- }
2376
-
2377
- // ../core/src/utils/shader.ts
2378
- function mergeShaders(target, source2) {
2379
- if (!source2) {
2380
- return target;
2381
- }
2382
- const result = { ...target, ...source2 };
2383
- if ("defines" in source2) {
2384
- result.defines = { ...target.defines, ...source2.defines };
2385
- }
2386
- if ("modules" in source2) {
2387
- result.modules = (target.modules || []).concat(source2.modules);
2388
- if (source2.modules.some((module2) => module2.name === "project64")) {
2389
- const index = result.modules.findIndex((module2) => module2.name === "project32");
2390
- if (index >= 0) {
2391
- result.modules.splice(index, 1);
2392
- }
2393
- }
2394
- }
2395
- if ("inject" in source2) {
2396
- if (!target.inject) {
2397
- result.inject = source2.inject;
2398
- } else {
2399
- const mergedInjection = { ...target.inject };
2400
- for (const key in source2.inject) {
2401
- mergedInjection[key] = (mergedInjection[key] || "") + source2.inject[key];
2402
- }
2403
- result.inject = mergedInjection;
2404
- }
2405
- }
2505
+ const result = import_core5.vec4.transformMat4([], vector, matrix);
2506
+ import_core5.vec4.scale(result, result, 1 / result[3]);
2406
2507
  return result;
2407
2508
  }
2408
2509
 
2409
2510
  // ../core/src/viewports/orthographic-viewport.ts
2410
- var import_core7 = require("@math.gl/core");
2411
- var import_web_mercator5 = require("@math.gl/web-mercator");
2412
- var viewMatrix = new import_core7.Matrix4().lookAt({ eye: [0, 0, 1] });
2511
+ var import_core6 = require("@math.gl/core");
2512
+ var import_web_mercator4 = require("@math.gl/web-mercator");
2513
+ var viewMatrix = new import_core6.Matrix4().lookAt({ eye: [0, 0, 1] });
2413
2514
  function getProjectionMatrix({
2414
2515
  width,
2415
2516
  height,
@@ -2423,14 +2524,14 @@ function getProjectionMatrix({
2423
2524
  let top = height / 2;
2424
2525
  if (padding) {
2425
2526
  const { left: l = 0, right: r = 0, top: t = 0, bottom: b = 0 } = padding;
2426
- const offsetX = (0, import_core7.clamp)((l + width - r) / 2, 0, width) - width / 2;
2427
- const offsetY = (0, import_core7.clamp)((t + height - b) / 2, 0, height) - height / 2;
2527
+ const offsetX = (0, import_core6.clamp)((l + width - r) / 2, 0, width) - width / 2;
2528
+ const offsetY = (0, import_core6.clamp)((t + height - b) / 2, 0, height) - height / 2;
2428
2529
  left -= offsetX;
2429
2530
  right -= offsetX;
2430
2531
  bottom += offsetY;
2431
2532
  top += offsetY;
2432
2533
  }
2433
- return new import_core7.Matrix4().ortho({
2534
+ return new import_core6.Matrix4().ortho({
2434
2535
  left,
2435
2536
  right,
2436
2537
  bottom,
@@ -2496,10 +2597,10 @@ var OrthographicViewport = class extends Viewport {
2496
2597
  }
2497
2598
  /* Needed by LinearInterpolator */
2498
2599
  panByPosition(coords, pixel, startPixel) {
2499
- const fromLocation = (0, import_web_mercator5.pixelsToWorld)(pixel, this.pixelUnprojectionMatrix);
2600
+ const fromLocation = (0, import_web_mercator4.pixelsToWorld)(pixel, this.pixelUnprojectionMatrix);
2500
2601
  const toLocation = this.projectFlat(coords);
2501
- const translate = import_core7.vec2.add([], toLocation, import_core7.vec2.negate([], fromLocation));
2502
- const newCenter = import_core7.vec2.add([], this.center, translate);
2602
+ const translate = import_core6.vec2.add([], toLocation, import_core6.vec2.negate([], fromLocation));
2603
+ const newCenter = import_core6.vec2.add([], this.center, translate);
2503
2604
  return { target: this.unprojectFlat(newCenter) };
2504
2605
  }
2505
2606
  };
@@ -3549,7 +3650,7 @@ Fp64Extension.extensionName = "Fp64Extension";
3549
3650
  var fp64_extension_default = Fp64Extension;
3550
3651
 
3551
3652
  // dist/path-style/path-style-extension.js
3552
- var import_core14 = require("@math.gl/core");
3653
+ var import_core13 = require("@math.gl/core");
3553
3654
 
3554
3655
  // dist/path-style/shaders.glsl.js
3555
3656
  var dashShaders = {
@@ -3557,26 +3658,68 @@ var dashShaders = {
3557
3658
  "vs:#decl": `
3558
3659
  in vec2 instanceDashArrays;
3559
3660
  #ifdef HIGH_PRECISION_DASH
3560
- in float instanceDashOffsets;
3661
+ in vec2 instanceDashOffsets;
3561
3662
  #endif
3562
3663
  out vec2 vDashArray;
3563
3664
  out float vDashOffset;
3665
+ out float vDashPathLength;
3666
+ layout(std140) uniform pathStyleUniforms {
3667
+ float dashAlignMode;
3668
+ bool dashGapPickable;
3669
+ highp int dashUnits;
3670
+ } pathStyle;
3564
3671
  `,
3565
3672
  "vs:#main-end": `
3566
- vDashArray = instanceDashArrays;
3673
+ float dashWidthPixels = path.billboard
3674
+ ? width.x * project.focalDistance
3675
+ : width.x * project.scale;
3676
+ float strokeHalfWidthPixels = path.billboard ? width.x : width.x * project.scale;
3677
+ float dashUnitPixels = strokeHalfWidthPixels;
3678
+ if (pathStyle.dashUnits == 1) {
3679
+ dashUnitPixels = 1.0;
3680
+ } else if (pathStyle.dashUnits == 2) {
3681
+ dashUnitPixels = project_size_to_pixel(1.0);
3682
+ } else if (pathStyle.dashUnits == 3) {
3683
+ dashUnitPixels = project.scale;
3684
+ }
3685
+ vDashArray = instanceDashArrays * (dashUnitPixels / dashWidthPixels);
3567
3686
  #ifdef HIGH_PRECISION_DASH
3568
- vDashOffset = instanceDashOffsets / width.x;
3687
+ vec2 dashOffsetAndLength = (instanceDashOffsets * project.scale) / dashWidthPixels;
3688
+ vDashPathLength = dashOffsetAndLength.y;
3689
+ float dashUnitLength = vDashArray.x + vDashArray.y;
3690
+ float dashPeriod = pathStyle.dashAlignMode == 0.0
3691
+ ? dashUnitLength
3692
+ : vDashPathLength / max(round(vDashPathLength / max(dashUnitLength, 0.0001)), 1.0);
3693
+ vDashOffset = dashPeriod > 0.0 ? mod(dashOffsetAndLength.x, dashPeriod) : 0.0;
3569
3694
  #else
3570
3695
  vDashOffset = 0.0;
3696
+ vDashPathLength = 0.0;
3571
3697
  #endif
3572
3698
  `,
3573
3699
  "fs:#decl": `
3574
3700
  layout(std140) uniform pathStyleUniforms {
3575
3701
  float dashAlignMode;
3576
3702
  bool dashGapPickable;
3703
+ highp int dashUnits;
3577
3704
  } pathStyle;
3578
3705
  in vec2 vDashArray;
3579
3706
  in float vDashOffset;
3707
+ in float vDashPathLength;
3708
+ float dashPatternIntegral(float position, float solidLength, float unitLength) {
3709
+ return floor(position / unitLength) * solidLength +
3710
+ min(mod(position, unitLength), solidLength);
3711
+ }
3712
+ float dashPatternCoverage(
3713
+ float position,
3714
+ float solidLength,
3715
+ float unitLength,
3716
+ float filterWidth
3717
+ ) {
3718
+ float halfFilter = 0.5 * filterWidth;
3719
+ float reducedPosition = mod(position, unitLength);
3720
+ return (dashPatternIntegral(reducedPosition + halfFilter, solidLength, unitLength) -
3721
+ dashPatternIntegral(reducedPosition - halfFilter, solidLength, unitLength)) / filterWidth;
3722
+ }
3580
3723
  `,
3581
3724
  // if given position is in the gap part of the dashed line
3582
3725
  // dashArray.x: solid stroke length, relative to width
@@ -3587,36 +3730,90 @@ in float vDashOffset;
3587
3730
  // 1 - stretch to fit, draw half dash at each end for nicer joints
3588
3731
  // o-- ---- ---- ---- --o-- --o-- ---- --o
3589
3732
  "fs:#main-start": `
3733
+ float dashCoverage = 1.0;
3734
+ bool shouldDiscardDash = false;
3735
+ bool inRoundedDashGap = false;
3736
+ float roundedDashResolvedCoverage = 1.0;
3737
+ float roundedDashSubPixelBlend = 0.0;
3738
+ float roundedDashDutyCycle = 1.0;
3590
3739
  float solidLength = vDashArray.x;
3591
3740
  float gapLength = vDashArray.y;
3592
3741
  float unitLength = solidLength + gapLength;
3742
+ if (unitLength > 0.0 && gapLength > 0.0) {
3593
3743
  float offset;
3594
- if (unitLength > 0.0) {
3595
3744
  if (pathStyle.dashAlignMode == 0.0) {
3596
3745
  offset = vDashOffset;
3597
3746
  } else {
3598
- unitLength = vPathLength / round(vPathLength / unitLength);
3747
+ #ifdef HIGH_PRECISION_DASH
3748
+ unitLength = vDashPathLength / max(round(vDashPathLength / unitLength), 1.0);
3749
+ #else
3750
+ unitLength = vPathLength / max(round(vPathLength / unitLength), 1.0);
3751
+ #endif
3752
+ solidLength = min(solidLength, unitLength);
3599
3753
  offset = solidLength / 2.0;
3754
+ #ifdef HIGH_PRECISION_DASH
3755
+ offset += vDashOffset;
3756
+ #endif
3600
3757
  }
3601
- float unitOffset = mod(vPathPosition.y + offset, unitLength);
3602
- if (gapLength > 0.0 && unitOffset > solidLength) {
3603
- if (path.capType <= 0.5) {
3604
- if (!(pathStyle.dashGapPickable && bool(picking.isActive))) {
3605
- discard;
3606
- }
3607
- } else {
3608
- float distToEnd = length(vec2(
3758
+ float alongPath = vPathPosition.y + offset;
3759
+ float unitOffset = mod(alongPath, unitLength);
3760
+ float filterWidth = max(fwidth(alongPath), 0.0001);
3761
+ if (bool(picking.isActive)) {
3762
+ bool inGap = unitOffset > solidLength;
3763
+ if (inGap && path.capType > 0.5) {
3764
+ inGap = length(vec2(
3609
3765
  min(unitOffset - solidLength, unitLength - unitOffset),
3610
3766
  vPathPosition.x
3611
- ));
3612
- if (distToEnd > 1.0) {
3613
- if (!(pathStyle.dashGapPickable && bool(picking.isActive))) {
3614
- discard;
3767
+ )) > 1.0;
3615
3768
  }
3769
+ if (inGap && !pathStyle.dashGapPickable) {
3770
+ shouldDiscardDash = true;
3616
3771
  }
3772
+ } else if (path.capType <= 0.5) {
3773
+ dashCoverage = dashPatternCoverage(alongPath, solidLength, unitLength, filterWidth);
3774
+ } else {
3775
+ float distanceAlongGap = min(unitOffset - solidLength, unitLength - unitOffset);
3776
+ float distanceToEnd = length(vec2(max(distanceAlongGap, 0.0), vPathPosition.x));
3777
+ float capEdgePixels = (1.0 - distanceToEnd) / max(fwidth(distanceToEnd), 1e-6);
3778
+ if (distanceAlongGap > 0.0) {
3779
+ inRoundedDashGap = true;
3780
+ roundedDashResolvedCoverage = smoothedge(0.0, capEdgePixels);
3781
+ dashCoverage = roundedDashResolvedCoverage;
3782
+ }
3783
+ float subPixelBlend = smoothstep(unitLength, 2.0 * unitLength, filterWidth);
3784
+ roundedDashSubPixelBlend = subPixelBlend;
3785
+ float boundedSolidLength = min(solidLength, unitLength);
3786
+ float effectiveGap = max(unitLength - boundedSolidLength, 0.0);
3787
+ float capSpan = 2.0 * sqrt(max(1.0 - vPathPosition.x * vPathPosition.x, 0.0));
3788
+ float roundedDutyCycle = clamp(
3789
+ (boundedSolidLength + min(effectiveGap, capSpan)) / unitLength,
3790
+ 0.0,
3791
+ 1.0
3792
+ );
3793
+ roundedDashDutyCycle = roundedDutyCycle;
3794
+ dashCoverage = mix(dashCoverage, roundedDutyCycle, subPixelBlend);
3617
3795
  }
3796
+ dashCoverage = clamp(dashCoverage, 0.0, 1.0);
3797
+ shouldDiscardDash = shouldDiscardDash || dashCoverage <= 0.0;
3618
3798
  }
3799
+ `,
3800
+ "fs:#main-end": `
3801
+ #ifdef ANTIALIASING
3802
+ if (inRoundedDashGap) {
3803
+ float pathCoverage = smoothedge(0.0, edgePixels);
3804
+ float resolvedCapMultiplier =
3805
+ min(pathCoverage, roundedDashResolvedCoverage) / max(pathCoverage, 1e-6);
3806
+ dashCoverage = mix(
3807
+ resolvedCapMultiplier,
3808
+ roundedDashDutyCycle,
3809
+ roundedDashSubPixelBlend
3810
+ );
3811
+ }
3812
+ #endif
3813
+ if (shouldDiscardDash) {
3814
+ discard;
3619
3815
  }
3816
+ fragColor.a *= dashCoverage;
3620
3817
  `
3621
3818
  }
3622
3819
  };
@@ -3817,13 +4014,32 @@ float offsetDir = sign(instanceOffsets);
3817
4014
  vPathPosition.x = (vPathPosition.x + offsetDir) * offsetWidth - offsetDir;
3818
4015
  vPathPosition.y *= offsetWidth;
3819
4016
  vPathLength *= offsetWidth;
4017
+ #ifdef DASH_ENABLED
4018
+ if (pathStyle.dashUnits != 0) {
4019
+ vDashArray *= offsetWidth;
4020
+ }
4021
+ vPathBounds *= offsetWidth;
4022
+ #ifdef HIGH_PRECISION_DASH
4023
+ vDashPathLength *= offsetWidth;
4024
+ float restoredDashUnitLength = vDashArray.x + vDashArray.y;
4025
+ float restoredDashPeriod = pathStyle.dashAlignMode == 0.0
4026
+ ? restoredDashUnitLength
4027
+ : vDashPathLength /
4028
+ max(round(vDashPathLength / max(restoredDashUnitLength, 0.0001)), 1.0);
4029
+ vDashOffset = restoredDashPeriod > 0.0
4030
+ ? mod(dashOffsetAndLength.x * offsetWidth, restoredDashPeriod)
4031
+ : 0.0;
4032
+ #else
4033
+ vDashOffset *= offsetWidth;
4034
+ #endif
4035
+ #endif
3820
4036
  `,
3821
- "fs:#main-start": `
3822
- float isInside;
3823
- isInside = step(-1.0, vPathPosition.x) * step(vPathPosition.x, 1.0);
3824
- if (isInside == 0.0) {
4037
+ "fs:#main-end": `
4038
+ #ifndef ANTIALIASING
4039
+ if (abs(vPathPosition.x) > 1.0) {
3825
4040
  discard;
3826
4041
  }
4042
+ #endif
3827
4043
  `
3828
4044
  }
3829
4045
  };
@@ -3833,11 +4049,56 @@ var defaultProps3 = {
3833
4049
  getDashArray: { type: "accessor", value: [0, 0] },
3834
4050
  getOffset: { type: "accessor", value: 0 },
3835
4051
  dashJustified: false,
3836
- dashGapPickable: false
4052
+ dashGapPickable: false,
4053
+ dashUnits: "widths"
3837
4054
  };
4055
+ var DASH_UNITS = {
4056
+ widths: 0,
4057
+ pixels: 1,
4058
+ meters: 2,
4059
+ common: 3
4060
+ };
4061
+ var PATH_STYLE_ATTRIBUTES = ["instanceDashArrays", "instanceDashOffsets", "instanceOffsets"];
4062
+ function dashMetricsProjectionPropsChanged({ props, oldProps }) {
4063
+ return !deepEqual(props.modelMatrix, oldProps.modelMatrix, 2) || props.coordinateSystem !== oldProps.coordinateSystem || !deepEqual(props.coordinateOrigin, oldProps.coordinateOrigin, 1);
4064
+ }
4065
+ function projectRenderedPathPosition(layer, position, projectUniforms) {
4066
+ const viewport = layer.context.viewport;
4067
+ if (!(viewport instanceof WebMercatorViewport) || viewport.zoom < 12) {
4068
+ return layer.projectPosition(position, { autoOffset: false });
4069
+ }
4070
+ const positionWorld = import_core13.vec4.transformMat4([], [position[0], position[1], position[2] || 0, 1], projectUniforms.modelMatrix);
4071
+ const coordinateSystem = layer.props.coordinateSystem === "default" ? "lnglat" : layer.props.coordinateSystem;
4072
+ if (coordinateSystem === "lnglat") {
4073
+ if (Math.abs(positionWorld[1] - projectUniforms.coordinateOrigin[1]) > 0.25) {
4074
+ let longitude = positionWorld[0];
4075
+ if (projectUniforms.wrapLongitude) {
4076
+ longitude = ((longitude + 180) % 360 + 360) % 360 - 180;
4077
+ }
4078
+ const commonPosition = viewport.projectPosition([longitude, positionWorld[1], 0]);
4079
+ return [
4080
+ commonPosition[0] - projectUniforms.commonOrigin[0],
4081
+ commonPosition[1] - projectUniforms.commonOrigin[1],
4082
+ positionWorld[2] * projectUniforms.commonUnitsPerMeter[2]
4083
+ ];
4084
+ }
4085
+ import_core13.vec3.sub(positionWorld, positionWorld, projectUniforms.coordinateOrigin);
4086
+ } else if (coordinateSystem === "cartesian") {
4087
+ import_core13.vec3.sub(positionWorld, positionWorld, projectUniforms.coordinateOrigin);
4088
+ }
4089
+ const deltaLatitude = positionWorld[1];
4090
+ const scale = import_core13.vec3.scaleAndAdd([], projectUniforms.commonUnitsPerWorldUnit, projectUniforms.commonUnitsPerWorldUnit2, deltaLatitude);
4091
+ return import_core13.vec3.multiply([], positionWorld, scale);
4092
+ }
3838
4093
  var PathStyleExtension = class extends LayerExtension {
3839
- constructor({ dash = false, offset = false, highPrecisionDash = false } = {}) {
3840
- super({ dash: dash || highPrecisionDash, offset, highPrecisionDash });
4094
+ constructor({ dash = false, offset = false, dashMode, highPrecisionDash = false } = {}) {
4095
+ const resolvedDashMode = dashMode ?? (highPrecisionDash ? "path" : "segment");
4096
+ super({
4097
+ dash: dash || highPrecisionDash || dashMode !== void 0,
4098
+ offset,
4099
+ dashMode: resolvedDashMode,
4100
+ highPrecisionDash: resolvedDashMode === "path"
4101
+ });
3841
4102
  }
3842
4103
  getLayerType(layer) {
3843
4104
  if ("pathTesselator" in layer.state) {
@@ -3852,6 +4113,59 @@ var PathStyleExtension = class extends LayerExtension {
3852
4113
  }
3853
4114
  return null;
3854
4115
  }
4116
+ synchronizeAttributes(layer) {
4117
+ const attributeManager = layer.getAttributeManager();
4118
+ const layerType = this.getLayerType(layer);
4119
+ if (!attributeManager || !layerType) {
4120
+ return false;
4121
+ }
4122
+ const attributes = attributeManager.getAttributes();
4123
+ const desiredAttributes = /* @__PURE__ */ new Set();
4124
+ if (this.opts.dash) {
4125
+ desiredAttributes.add("instanceDashArrays");
4126
+ }
4127
+ if (layerType === "path" && this.opts.dash && this.opts.dashMode === "path") {
4128
+ desiredAttributes.add("instanceDashOffsets");
4129
+ }
4130
+ if (layerType === "path" && this.opts.offset) {
4131
+ desiredAttributes.add("instanceOffsets");
4132
+ }
4133
+ let topologyChanged = false;
4134
+ for (const attributeName of PATH_STYLE_ATTRIBUTES) {
4135
+ if (attributes[attributeName] && !desiredAttributes.has(attributeName)) {
4136
+ attributeManager.remove([attributeName]);
4137
+ topologyChanged = true;
4138
+ }
4139
+ }
4140
+ if (desiredAttributes.has("instanceDashArrays") && !attributes.instanceDashArrays) {
4141
+ attributeManager.addInstanced({
4142
+ instanceDashArrays: { size: 2, accessor: "getDashArray" }
4143
+ });
4144
+ topologyChanged = true;
4145
+ }
4146
+ if (desiredAttributes.has("instanceDashOffsets") && !attributes.instanceDashOffsets) {
4147
+ attributeManager.addInstanced({
4148
+ instanceDashOffsets: {
4149
+ // [distance from the start of the path, total length of the path]
4150
+ size: 2,
4151
+ // Recalculate from rendered geometry when getPath changes. A binary getPath buffer
4152
+ // contains positions, not dash metrics, so keep it as an update trigger rather than
4153
+ // binding it directly to this attribute.
4154
+ accessor: ["getPath"],
4155
+ // eslint-disable-next-line @typescript-eslint/unbound-method
4156
+ update: this.calculateDashMetrics
4157
+ }
4158
+ });
4159
+ topologyChanged = true;
4160
+ }
4161
+ if (desiredAttributes.has("instanceOffsets") && !attributes.instanceOffsets) {
4162
+ attributeManager.addInstanced({
4163
+ instanceOffsets: { size: 1, accessor: "getOffset" }
4164
+ });
4165
+ topologyChanged = true;
4166
+ }
4167
+ return topologyChanged;
4168
+ }
3855
4169
  isEnabled(layer) {
3856
4170
  return this.getLayerType(layer) !== null;
3857
4171
  }
@@ -3878,57 +4192,55 @@ var PathStyleExtension = class extends LayerExtension {
3878
4192
  const defines2 = {};
3879
4193
  if (extension.opts.dash) {
3880
4194
  result = mergeShaders(result, dashShaders);
3881
- if (extension.opts.highPrecisionDash) {
4195
+ defines2.DASH_ENABLED = true;
4196
+ if (extension.opts.dashMode === "path") {
3882
4197
  defines2.HIGH_PRECISION_DASH = true;
3883
4198
  }
3884
4199
  }
3885
4200
  if (extension.opts.offset) {
3886
4201
  result = mergeShaders(result, offsetShaders);
4202
+ defines2.PATH_STYLE_OFFSET = true;
3887
4203
  }
3888
4204
  const { inject: inject6 } = result;
3889
4205
  const pathStyle = {
3890
4206
  name: "pathStyle",
3891
- inject: inject6,
3892
- uniformTypes: {
3893
- dashAlignMode: "f32",
3894
- dashGapPickable: "i32"
3895
- }
4207
+ inject: inject6
3896
4208
  };
4209
+ if (extension.opts.dash) {
4210
+ pathStyle.uniformTypes = {
4211
+ dashAlignMode: "f32",
4212
+ dashGapPickable: "i32",
4213
+ dashUnits: "i32"
4214
+ };
4215
+ }
3897
4216
  return {
3898
4217
  modules: [pathStyle],
3899
4218
  defines: defines2
3900
4219
  };
3901
4220
  }
3902
4221
  initializeState(context, extension) {
3903
- const attributeManager = this.getAttributeManager();
3904
- const layerType = extension.getLayerType(this);
3905
- if (!attributeManager || !layerType) {
3906
- return;
3907
- }
3908
- if (extension.opts.dash) {
3909
- attributeManager.addInstanced({
3910
- instanceDashArrays: { size: 2, accessor: "getDashArray" },
3911
- ...layerType === "path" && extension.opts.highPrecisionDash ? {
3912
- instanceDashOffsets: {
3913
- size: 1,
3914
- accessor: "getPath",
3915
- transform: extension.getDashOffsets.bind(this)
3916
- }
3917
- } : {}
3918
- });
3919
- }
3920
- if (layerType === "path" && extension.opts.offset) {
3921
- attributeManager.addInstanced({
3922
- instanceOffsets: { size: 1, accessor: "getOffset" }
3923
- });
3924
- }
4222
+ extension.synchronizeAttributes(this);
3925
4223
  }
3926
4224
  updateState(params, extension) {
4225
+ var _a;
3927
4226
  if (!extension.isEnabled(this)) {
3928
4227
  return;
3929
4228
  }
4229
+ if (params.changeFlags.extensionsChanged) {
4230
+ const topologyChanged = extension.synchronizeAttributes(this);
4231
+ const attributeManager = this.getAttributeManager();
4232
+ if (attributeManager && topologyChanged) {
4233
+ attributeManager.invalidateAll();
4234
+ for (const model of this.getModels()) {
4235
+ model.setBufferLayout(attributeManager.getBufferLayouts(model));
4236
+ }
4237
+ }
4238
+ }
3930
4239
  if (extension.opts.dash) {
3931
4240
  const layerType = extension.getLayerType(this);
4241
+ if (layerType === "path" && extension.opts.dashMode === "path" && dashMetricsProjectionPropsChanged(params)) {
4242
+ (_a = this.getAttributeManager()) == null ? void 0 : _a.invalidate("instanceDashOffsets");
4243
+ }
3932
4244
  if (layerType === "scatterplot" || layerType === "textBackground") {
3933
4245
  const pathStyleProps = {
3934
4246
  dashGapPickable: Boolean(this.props.dashGapPickable)
@@ -3937,12 +4249,96 @@ var PathStyleExtension = class extends LayerExtension {
3937
4249
  } else {
3938
4250
  const pathStyleProps = {
3939
4251
  dashAlignMode: this.props.dashJustified ? 1 : 0,
3940
- dashGapPickable: Boolean(this.props.dashGapPickable)
4252
+ dashGapPickable: Boolean(this.props.dashGapPickable),
4253
+ dashUnits: DASH_UNITS[this.props.dashUnits ?? "widths"]
3941
4254
  };
3942
4255
  this.setShaderModuleProps({ pathStyle: pathStyleProps });
3943
4256
  }
3944
4257
  }
3945
4258
  }
4259
+ calculateDashMetrics(attribute, { startRow, endRow }) {
4260
+ var _a, _b, _c;
4261
+ const pathTesselator = this.state.pathTesselator;
4262
+ const vertexStarts = pathTesselator == null ? void 0 : pathTesselator.vertexStarts;
4263
+ const instanceCount = pathTesselator == null ? void 0 : pathTesselator.instanceCount;
4264
+ const packedPositions = pathTesselator == null ? void 0 : pathTesselator.get("positions");
4265
+ if (!vertexStarts || instanceCount === void 0 || typeof (pathTesselator == null ? void 0 : pathTesselator.getPathSegmentIndices) !== "function") {
4266
+ throw new Error("PathStyleExtension requires PathLayer tessellation data for path dashes.");
4267
+ }
4268
+ const binaryPath = (_b = (_a = this.props.data) == null ? void 0 : _a.attributes) == null ? void 0 : _b.getPath;
4269
+ const binaryValue = ArrayBuffer.isView(binaryPath) ? binaryPath : binaryPath == null ? void 0 : binaryPath.value;
4270
+ const hasReadableBinaryPath = ArrayBuffer.isView(binaryValue);
4271
+ const usePackedPositions = packedPositions && (!binaryPath || pathTesselator.normalize || ((_c = pathTesselator.opts) == null ? void 0 : _c.isWebGPU));
4272
+ if (binaryPath && !hasReadableBinaryPath || !usePackedPositions && !hasReadableBinaryPath) {
4273
+ throw new Error("PathStyleExtension cannot calculate whole-path dash metrics from GPU-only getPath data; supply data.attributes.instanceDashOffsets.");
4274
+ }
4275
+ const output = attribute.value;
4276
+ const metricSize = attribute.size;
4277
+ const firstRow = Math.max(0, startRow);
4278
+ const lastRow = Math.min(endRow, vertexStarts.length - 1);
4279
+ let binarySize = 3;
4280
+ let binaryStride = 3;
4281
+ let binaryOffset = 0;
4282
+ if (!usePackedPositions) {
4283
+ const bytesPerElement = binaryValue.BYTES_PER_ELEMENT;
4284
+ binarySize = (binaryPath == null ? void 0 : binaryPath.size) || (this.props.positionFormat === "XY" ? 2 : 3);
4285
+ binaryStride = (binaryPath == null ? void 0 : binaryPath.stride) ? binaryPath.stride / bytesPerElement : binarySize;
4286
+ binaryOffset = (binaryPath == null ? void 0 : binaryPath.offset) ? binaryPath.offset / bytesPerElement : 0;
4287
+ }
4288
+ const sourcePosition = new Array(3);
4289
+ const projectUniforms = project_default.getUniforms({
4290
+ viewport: this.context.viewport,
4291
+ modelMatrix: this.props.modelMatrix,
4292
+ coordinateSystem: this.props.coordinateSystem,
4293
+ coordinateOrigin: this.props.coordinateOrigin,
4294
+ // PathLayer normalizes/cuts wrapped paths on the CPU; its shader never wraps again.
4295
+ autoWrapLongitude: this.wrapLongitude
4296
+ });
4297
+ const readPosition = (vertexIndex) => {
4298
+ if (usePackedPositions) {
4299
+ sourcePosition[0] = packedPositions[vertexIndex * 3];
4300
+ sourcePosition[1] = packedPositions[vertexIndex * 3 + 1];
4301
+ sourcePosition[2] = packedPositions[vertexIndex * 3 + 2];
4302
+ } else {
4303
+ const sourceIndex = binaryOffset + vertexIndex * binaryStride;
4304
+ sourcePosition[0] = binaryValue[sourceIndex];
4305
+ sourcePosition[1] = binaryValue[sourceIndex + 1];
4306
+ sourcePosition[2] = binarySize === 3 ? binaryValue[sourceIndex + 2] : 0;
4307
+ }
4308
+ return sourcePosition;
4309
+ };
4310
+ attribute.startIndices = vertexStarts;
4311
+ for (let row = firstRow; row < lastRow; row++) {
4312
+ const rowStart = vertexStarts[row];
4313
+ const rowEnd = Math.min(vertexStarts[row + 1] ?? instanceCount, instanceCount);
4314
+ output.fill(0, rowStart * metricSize, rowEnd * metricSize);
4315
+ const validSegments = pathTesselator.getPathSegmentIndices(row);
4316
+ let phase = 0;
4317
+ let previousSegment = -2;
4318
+ let previousEnd = null;
4319
+ for (const segmentIndex of validSegments) {
4320
+ const outputIndex = segmentIndex * metricSize;
4321
+ output[outputIndex] = phase;
4322
+ const start = segmentIndex === previousSegment + 1 && previousEnd ? previousEnd : projectRenderedPathPosition(this, readPosition(segmentIndex), projectUniforms);
4323
+ const end = projectRenderedPathPosition(this, readPosition(segmentIndex + 1), projectUniforms);
4324
+ phase += import_core13.vec3.dist(start, end);
4325
+ previousSegment = segmentIndex;
4326
+ previousEnd = end;
4327
+ }
4328
+ if (metricSize > 1) {
4329
+ for (let vertexIndex = rowStart; vertexIndex < rowEnd; vertexIndex++) {
4330
+ output[vertexIndex * metricSize + 1] = phase;
4331
+ }
4332
+ }
4333
+ }
4334
+ }
4335
+ /**
4336
+ * Calculates the distance from the start of a path to each rendered segment.
4337
+ *
4338
+ * The final entry is zero because PathLayer reserves the final vertex as invalid padding.
4339
+ * This scalar return shape is retained for compatibility; path-mode rendering uses an
4340
+ * internal attribute containing both each segment offset and the total path length.
4341
+ */
3946
4342
  getDashOffsets(path) {
3947
4343
  const result = [0];
3948
4344
  const positionSize = this.props.positionFormat === "XY" ? 2 : 3;
@@ -3952,9 +4348,9 @@ var PathStyleExtension = class extends LayerExtension {
3952
4348
  let prevP;
3953
4349
  for (let i = 0; i < geometrySize - 1; i++) {
3954
4350
  p = isNested ? path[i] : path.slice(i * positionSize, i * positionSize + positionSize);
3955
- p = this.projectPosition(p);
4351
+ p = this.projectPosition(p, { autoOffset: false });
3956
4352
  if (i > 0) {
3957
- result[i] = result[i - 1] + import_core14.vec3.dist(prevP, p);
4353
+ result[i] = result[i - 1] + import_core13.vec3.dist(prevP, p);
3958
4354
  }
3959
4355
  prevP = p;
3960
4356
  }
@@ -3967,12 +4363,26 @@ PathStyleExtension.extensionName = "PathStyleExtension";
3967
4363
  var path_style_extension_default = PathStyleExtension;
3968
4364
 
3969
4365
  // dist/fill-style/shader-module.js
4366
+ var FILL_UV_SCALE = 512 / 4e7;
4367
+ function getPatternUnitScale(sizeUnits, viewport) {
4368
+ switch (sizeUnits) {
4369
+ case "pixels":
4370
+ return 2 ** -Math.round(viewport.zoom);
4371
+ case "common":
4372
+ return 1;
4373
+ default:
4374
+ return FILL_UV_SCALE;
4375
+ }
4376
+ }
3970
4377
  var uniformBlock3 = (
3971
4378
  /* glsl */
3972
4379
  `layout(std140) uniform fillUniforms {
3973
4380
  vec2 patternTextureSize;
4381
+ float patternUnitScale;
3974
4382
  bool patternEnabled;
3975
4383
  bool patternMask;
4384
+ bool procedural;
4385
+ bool flipY;
3976
4386
  vec2 uvCoordinateOrigin;
3977
4387
  vec2 uvCoordinateOrigin64Low;
3978
4388
  } fill;
@@ -3981,13 +4391,20 @@ var uniformBlock3 = (
3981
4391
  var patternVs = (
3982
4392
  /* glsl */
3983
4393
  `
4394
+ uniform sampler2D fill_patternTexture;
4395
+
3984
4396
  in vec4 fillPatternFrames;
3985
4397
  in float fillPatternScales;
3986
4398
  in vec2 fillPatternOffsets;
4399
+ in vec4 fillPatternBackgroundColors;
3987
4400
 
3988
4401
  out vec2 fill_uv;
3989
4402
  out vec4 fill_patternBounds;
3990
4403
  out vec4 fill_patternPlacement;
4404
+ out vec4 fill_backgroundColor;
4405
+ out float fill_patternCoordinateScale;
4406
+ flat out vec4 fill_patternParams0;
4407
+ flat out vec4 fill_patternParams1;
3991
4408
  `
3992
4409
  );
3993
4410
  var vs4 = `
@@ -4001,9 +4418,136 @@ uniform sampler2D fill_patternTexture;
4001
4418
 
4002
4419
  in vec4 fill_patternBounds;
4003
4420
  in vec4 fill_patternPlacement;
4421
+ in vec4 fill_backgroundColor;
4004
4422
  in vec2 fill_uv;
4423
+ in float fill_patternCoordinateScale;
4424
+ flat in vec4 fill_patternParams0;
4425
+ flat in vec4 fill_patternParams1;
4426
+
4427
+ const float FILL_PATTERN_HATCH = 1.0;
4428
+ const float FILL_PATTERN_CROSS_HATCH = 2.0;
4429
+ const float FILL_PATTERN_DOTS = 3.0;
4430
+
4431
+ float fill_getPatternCoordinate(vec2 axis, float period, float offset) {
4432
+ float coordinateScale = max(abs(fill_patternCoordinateScale), 1.0e-20);
4433
+ vec2 coordinateOrigin = fill.uvCoordinateOrigin;
4434
+ vec2 coordinateOffset = fill.uvCoordinateOrigin64Low + fill_uv;
4435
+ float yDirection = fill.flipY ? 1.0 : -1.0;
4436
+ coordinateOrigin.y *= yDirection;
4437
+ coordinateOffset.y *= yDirection;
4438
+
4439
+ // Reduce the high part before adding the local offset to retain precision in large coordinates.
4440
+ float highPart = mod(dot(coordinateOrigin, axis) / coordinateScale, period);
4441
+ float lowPart = dot(coordinateOffset, axis) / coordinateScale;
4442
+ return mod(highPart + lowPart + offset * period, period);
4443
+ }
4444
+
4445
+ vec2 fill_getPatternCoords(
4446
+ vec2 axis0,
4447
+ vec2 axis1,
4448
+ vec2 period,
4449
+ vec2 offset
4450
+ ) {
4451
+ return vec2(
4452
+ fill_getPatternCoordinate(axis0, period.x, offset.x),
4453
+ fill_getPatternCoordinate(axis1, period.y, offset.y)
4454
+ );
4455
+ }
4456
+
4457
+ float fill_getScreenSpaceAlpha(float signedDistance) {
4458
+ float distanceDerivative = fwidth(signedDistance);
4459
+ if (distanceDerivative == 0.0) {
4460
+ return signedDistance <= 0.0 ? 1.0 : 0.0;
4461
+ }
4462
+ // Normalize the SDF to screen pixels so the coverage ramp is independent of pattern units.
4463
+ float distanceInPixels = signedDistance / distanceDerivative;
4464
+ return 1.0 - smoothstep(-0.5, 0.5, distanceInPixels);
4465
+ }
4466
+
4467
+ float fill_getLineAlpha(float position, float center, float period, float strokeWidth) {
4468
+ float distanceToLine = abs(mod(position - center + period * 0.5, period) - period * 0.5);
4469
+ return fill_getScreenSpaceAlpha(distanceToLine - strokeWidth * 0.5);
4470
+ }
4471
+
4472
+ float fill_getHatchAlpha(float angle, float strokeWidth, vec2 gaps, bool alternating) {
4473
+ vec2 lineDirection = vec2(cos(angle), sin(angle));
4474
+ vec2 lineNormal = vec2(-lineDirection.y, lineDirection.x);
4475
+ float period = alternating
4476
+ ? strokeWidth * 2.0 + gaps.x + gaps.y
4477
+ : strokeWidth + gaps.x;
4478
+ float offset = dot(fill_patternPlacement.xy, lineNormal);
4479
+ float position = fill_getPatternCoordinate(lineNormal, period, offset);
4480
+ float alpha = fill_getLineAlpha(position, 0.0, period, strokeWidth);
4481
+ if (alternating) {
4482
+ alpha = max(
4483
+ alpha,
4484
+ fill_getLineAlpha(position, strokeWidth + gaps.x, period, strokeWidth)
4485
+ );
4486
+ }
4487
+ return alpha;
4488
+ }
4489
+
4490
+ float fill_getDotAlpha(float angle, float skew, float radius, float gap) {
4491
+ vec2 axis0 = vec2(cos(angle), sin(angle));
4492
+ float axis1Angle = angle + 1.5707963267948966 - skew;
4493
+ vec2 axis1 = vec2(cos(axis1Angle), sin(axis1Angle));
4494
+ float determinant = axis0.x * axis1.y - axis0.y * axis1.x;
4005
4495
 
4006
- const float FILL_UV_SCALE = 512.0 / 40000000.0;
4496
+ // The reciprocal basis maps common coordinates into the skewed dot lattice.
4497
+ vec2 reciprocal0 = vec2(axis1.y, -axis1.x) / determinant;
4498
+ vec2 reciprocal1 = vec2(-axis0.y, axis0.x) / determinant;
4499
+ float period = radius * 2.0 + gap;
4500
+ vec2 latticeCoords = fill_getPatternCoords(
4501
+ reciprocal0,
4502
+ reciprocal1,
4503
+ vec2(period),
4504
+ fill_patternPlacement.xy
4505
+ );
4506
+ vec2 cellOffset = mod(latticeCoords + period * 0.5, period) - period * 0.5;
4507
+ float distanceToDotSquared = 1.0e20;
4508
+ // In a skewed lattice the closest dot is not necessarily in the independently wrapped cell.
4509
+ // Include adjacent cells to keep the repeated distance field continuous at cell boundaries.
4510
+ for (int x = -1; x <= 1; x++) {
4511
+ for (int y = -1; y <= 1; y++) {
4512
+ vec2 neighborOffset = cellOffset + vec2(float(x), float(y)) * period;
4513
+ vec2 dotOffset = axis0 * neighborOffset.x + axis1 * neighborOffset.y;
4514
+ distanceToDotSquared = min(distanceToDotSquared, dot(dotOffset, dotOffset));
4515
+ }
4516
+ }
4517
+ float distanceToDot = sqrt(distanceToDotSquared);
4518
+ return fill_getScreenSpaceAlpha(distanceToDot - radius);
4519
+ }
4520
+
4521
+ float fill_getProceduralPatternAlpha() {
4522
+ float patternType = fill_patternParams0.x;
4523
+ float size = fill_patternParams0.y;
4524
+ vec2 gaps = fill_patternParams0.zw;
4525
+ vec2 angles = fill_patternParams1.xy;
4526
+ float elementCount = fill_patternParams1.z;
4527
+
4528
+ if (patternType == FILL_PATTERN_HATCH) {
4529
+ return fill_getHatchAlpha(angles.x, size, gaps, elementCount == 2.0);
4530
+ }
4531
+ if (patternType == FILL_PATTERN_CROSS_HATCH) {
4532
+ return max(
4533
+ fill_getHatchAlpha(angles.x, size, gaps, false),
4534
+ fill_getHatchAlpha(angles.y, size, gaps, false)
4535
+ );
4536
+ }
4537
+ if (patternType == FILL_PATTERN_DOTS) {
4538
+ return fill_getDotAlpha(angles.x, angles.y, size, gaps.x);
4539
+ }
4540
+ return 1.0;
4541
+ }
4542
+
4543
+ // Draw the pattern over the background fill (Porter-Duff source-over)
4544
+ vec4 fill_blendOverBackground(vec4 pattern, vec4 background) {
4545
+ if (background.a == 0.0 || layer.opacity == 0.0) return pattern;
4546
+ float patternAlpha = pattern.a / layer.opacity;
4547
+ float blendedAlpha = patternAlpha + background.a * (1.0 - patternAlpha);
4548
+ vec3 blendedRGB = mix(background.rgb, pattern.rgb, patternAlpha / blendedAlpha);
4549
+ return vec4(blendedRGB, blendedAlpha * layer.opacity);
4550
+ }
4007
4551
  `
4008
4552
  );
4009
4553
  var fs4 = `
@@ -4012,18 +4556,34 @@ ${patternFs}
4012
4556
  `;
4013
4557
  var inject3 = {
4014
4558
  "vs:DECKGL_FILTER_GL_POSITION": (
4015
- /* glsl */
4016
- `
4017
- fill_uv = geometry.position.xy;
4018
- `
4019
- ),
4020
- "vs:DECKGL_FILTER_COLOR": (
4021
4559
  /* glsl */
4022
4560
  `
4023
4561
  if (fill.patternEnabled) {
4024
- fill_patternBounds = fillPatternFrames / vec4(fill.patternTextureSize, fill.patternTextureSize);
4025
4562
  fill_patternPlacement.xy = fillPatternOffsets;
4026
- fill_patternPlacement.zw = fillPatternScales * fillPatternFrames.zw;
4563
+ fill_backgroundColor = fillPatternBackgroundColors;
4564
+ if (fill.procedural) {
4565
+ fill_uv = geometry.position.xy;
4566
+ fill_patternCoordinateScale = fill.patternUnitScale * fillPatternScales;
4567
+ int texelIndex = int(fillPatternFrames.x) * 4;
4568
+ fill_patternParams0 = vec4(
4569
+ texelFetch(fill_patternTexture, ivec2(texelIndex, 0), 0).rg,
4570
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 1, 0), 0).rg
4571
+ );
4572
+ fill_patternParams1 = vec4(
4573
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 2, 0), 0).rg,
4574
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 3, 0), 0).rg
4575
+ );
4576
+ } else {
4577
+ fill_patternBounds = fillPatternFrames / vec4(fill.patternTextureSize, fill.patternTextureSize);
4578
+ vec2 patternFrameCommon = fill.patternUnitScale * fillPatternScales * fillPatternFrames.zw;
4579
+ // Reduce the coordinate origin to within one tile before adding the vertex position. The
4580
+ // origin is large in common space, and fp32 cannot carry the sum at full precision.
4581
+ vec2 origin = mod(fill.uvCoordinateOrigin, patternFrameCommon) + fill.uvCoordinateOrigin64Low;
4582
+ fill_uv = (origin + geometry.position.xy) / patternFrameCommon;
4583
+ // Pattern atlases use top-left coordinates, so reverse common-space Y in bottom-left views.
4584
+ fill_uv.y *= fill.flipY ? 1.0 : -1.0;
4585
+ fill_uv += fillPatternOffsets;
4586
+ }
4027
4587
  }
4028
4588
  `
4029
4589
  ),
@@ -4031,22 +4591,31 @@ var inject3 = {
4031
4591
  /* glsl */
4032
4592
  `
4033
4593
  if (fill.patternEnabled) {
4034
- vec2 scale = FILL_UV_SCALE * fill_patternPlacement.zw;
4035
- vec2 patternUV = mod(mod(fill.uvCoordinateOrigin, scale) + fill.uvCoordinateOrigin64Low + fill_uv, scale) / scale;
4036
- patternUV = mod(fill_patternPlacement.xy + patternUV, 1.0);
4037
-
4038
- vec2 texCoords = fill_patternBounds.xy + fill_patternBounds.zw * patternUV;
4039
-
4040
- vec4 patternColor = texture(fill_patternTexture, texCoords);
4041
- color.a *= patternColor.a;
4042
- if (!fill.patternMask) {
4043
- color.rgb = patternColor.rgb;
4594
+ if (fill.procedural) {
4595
+ color.a *= fill_getProceduralPatternAlpha();
4596
+ } else {
4597
+ vec2 patternUV = fract(fill_uv);
4598
+ vec2 texCoords = fill_patternBounds.xy + fill_patternBounds.zw * patternUV;
4599
+
4600
+ // Tiling is emulated by wrapping the coordinate, so texCoords jumps from the end of the
4601
+ // frame back to its start once per tile, leading to the wrong mip level being selected,
4602
+ // which leads to artifacts at pattern edges. fill_uv is continuous across the primitive,
4603
+ // meaning that the correct mip level is always selected
4604
+ vec4 grad = fill_patternBounds.zwzw * vec4(dFdx(fill_uv), dFdy(fill_uv));
4605
+ vec4 patternColor = textureGrad(fill_patternTexture, texCoords, grad.xy, grad.zw);
4606
+
4607
+ color.a *= patternColor.a;
4608
+ if (!fill.patternMask) {
4609
+ color.rgb = patternColor.rgb;
4610
+ }
4044
4611
  }
4612
+ color = fill_blendOverBackground(color, fill_backgroundColor);
4045
4613
  }
4046
4614
  `
4047
4615
  )
4048
4616
  };
4049
4617
  function getPatternUniforms(opts) {
4618
+ var _a;
4050
4619
  if (!opts) {
4051
4620
  return {};
4052
4621
  }
@@ -4057,17 +4626,22 @@ function getPatternUniforms(opts) {
4057
4626
  uniforms.patternTextureSize = [fillPatternTexture.width, fillPatternTexture.height];
4058
4627
  }
4059
4628
  if ("project" in opts) {
4060
- const { fillPatternMask = true, fillPatternEnabled = true } = opts;
4629
+ const { fillPatternMask = true, fillPatternEnabled = true, fillPatternSizeUnits = "meters", fillPatternCommonFrame = null, procedural = false } = opts;
4061
4630
  const projectUniforms = project_default.getUniforms(opts.project);
4062
4631
  const { commonOrigin: coordinateOriginCommon } = projectUniforms;
4063
- const coordinateOriginCommon64Low = [
4064
- fp64LowPart(coordinateOriginCommon[0]),
4065
- fp64LowPart(coordinateOriginCommon[1])
4066
- ];
4067
- uniforms.uvCoordinateOrigin = coordinateOriginCommon.slice(0, 2);
4068
- uniforms.uvCoordinateOrigin64Low = coordinateOriginCommon64Low;
4632
+ const unitScale = getPatternUnitScale(fillPatternSizeUnits, opts.project.viewport);
4633
+ const origin = [coordinateOriginCommon[0], coordinateOriginCommon[1]];
4634
+ if (fillPatternCommonFrame) {
4635
+ origin[0] %= unitScale * fillPatternCommonFrame[0];
4636
+ origin[1] %= unitScale * fillPatternCommonFrame[1];
4637
+ }
4638
+ uniforms.uvCoordinateOrigin = origin;
4639
+ uniforms.uvCoordinateOrigin64Low = [fp64LowPart(origin[0]), fp64LowPart(origin[1])];
4640
+ uniforms.patternUnitScale = unitScale;
4069
4641
  uniforms.patternMask = fillPatternMask;
4070
4642
  uniforms.patternEnabled = fillPatternEnabled;
4643
+ uniforms.procedural = procedural;
4644
+ uniforms.flipY = ((_a = opts.project.viewport) == null ? void 0 : _a.flipY) ?? false;
4071
4645
  }
4072
4646
  return uniforms;
4073
4647
  }
@@ -4080,31 +4654,172 @@ var patternShaders = {
4080
4654
  getUniforms: getPatternUniforms,
4081
4655
  uniformTypes: {
4082
4656
  patternTextureSize: "vec2<f32>",
4657
+ patternUnitScale: "f32",
4083
4658
  patternEnabled: "i32",
4084
4659
  patternMask: "i32",
4660
+ procedural: "i32",
4661
+ flipY: "i32",
4085
4662
  uvCoordinateOrigin: "vec2<f32>",
4086
4663
  uvCoordinateOrigin64Low: "vec2<f32>"
4087
4664
  }
4088
4665
  };
4089
4666
 
4667
+ // dist/fill-style/procedural-pattern.js
4668
+ var PROCEDURAL_PATTERN_TEXTURE_FORMAT = "rg32float";
4669
+ var PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL = 2;
4670
+ var PROCEDURAL_PATTERN_TEXELS_PER_PATTERN = 4;
4671
+ var PROCEDURAL_PATTERN_TYPES = {
4672
+ none: 0,
4673
+ hatch: 1,
4674
+ crossHatch: 2,
4675
+ dots: 3
4676
+ };
4677
+ var PROCEDURAL_PATTERN_OFFSETS = {
4678
+ type: 0,
4679
+ size: 1,
4680
+ gap0: 2,
4681
+ gap1: 3,
4682
+ angle0: 4,
4683
+ angle1: 5,
4684
+ /** Number of alternating gaps, hatch directions, or dot-grid axes, depending on type. */
4685
+ elementCount: 6
4686
+ };
4687
+ var COMPONENTS_PER_PATTERN = PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL * PROCEDURAL_PATTERN_TEXELS_PER_PATTERN;
4688
+ var DEGREES_TO_RADIANS3 = Math.PI / 180;
4689
+ var DEFAULT_SIZE = 1;
4690
+ var DEFAULT_GAP = 1;
4691
+ var DEFAULT_HATCH_ANGLE = 0;
4692
+ var DEFAULT_CROSS_HATCH_ANGLES = [45, 135];
4693
+ var DEFAULT_DOT_ANGLE = 0;
4694
+ var DEFAULT_DOT_SKEW = 0;
4695
+ function packProceduralPatterns(mapping) {
4696
+ const entries = Object.entries(mapping);
4697
+ const data = new Float32Array((entries.length + 1) * COMPONENTS_PER_PATTERN);
4698
+ const patternIndices = /* @__PURE__ */ new Map();
4699
+ entries.forEach(([id, config], entryIndex) => {
4700
+ const patternIndex = entryIndex + 1;
4701
+ const offset = patternIndex * COMPONENTS_PER_PATTERN;
4702
+ patternIndices.set(id, patternIndex);
4703
+ switch (config.type) {
4704
+ case "hatch": {
4705
+ const gap = config.gap === void 0 ? DEFAULT_GAP : config.gap;
4706
+ const strokeWidth = config.strokeWidth === void 0 ? DEFAULT_SIZE : config.strokeWidth;
4707
+ const angle = config.angle === void 0 ? DEFAULT_HATCH_ANGLE : config.angle;
4708
+ if (Array.isArray(gap) && gap.length !== 2) {
4709
+ throw new Error(`${id}.gap must be a number or a two-element array`);
4710
+ }
4711
+ const gaps = Array.isArray(gap) ? gap : [gap, gap];
4712
+ validatePositive(strokeWidth, `${id}.strokeWidth`);
4713
+ validateNonNegative(gaps[0], `${id}.gap[0]`);
4714
+ validateNonNegative(gaps[1], `${id}.gap[1]`);
4715
+ validateAngle(angle, `${id}.angle`);
4716
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.hatch;
4717
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = strokeWidth;
4718
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gaps[0];
4719
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gaps[1];
4720
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angle * DEGREES_TO_RADIANS3;
4721
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = Array.isArray(gap) ? 2 : 1;
4722
+ break;
4723
+ }
4724
+ case "cross-hatch": {
4725
+ const angles = config.angles === void 0 ? DEFAULT_CROSS_HATCH_ANGLES : config.angles;
4726
+ const strokeWidth = config.strokeWidth === void 0 ? DEFAULT_SIZE : config.strokeWidth;
4727
+ const gap = config.gap === void 0 ? DEFAULT_GAP : config.gap;
4728
+ if (!Array.isArray(angles) || angles.length !== 2) {
4729
+ throw new Error(`${id}.angles must be a two-element array`);
4730
+ }
4731
+ validatePositive(strokeWidth, `${id}.strokeWidth`);
4732
+ validateNonNegative(gap, `${id}.gap`);
4733
+ validateAngle(angles[0], `${id}.angles[0]`);
4734
+ validateAngle(angles[1], `${id}.angles[1]`);
4735
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.crossHatch;
4736
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = strokeWidth;
4737
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gap;
4738
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gap;
4739
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angles[0] * DEGREES_TO_RADIANS3;
4740
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle1] = angles[1] * DEGREES_TO_RADIANS3;
4741
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = 2;
4742
+ break;
4743
+ }
4744
+ case "dots": {
4745
+ const radius = config.radius === void 0 ? DEFAULT_SIZE : config.radius;
4746
+ const gap = config.gap === void 0 ? DEFAULT_GAP : config.gap;
4747
+ const angle = config.angle === void 0 ? DEFAULT_DOT_ANGLE : config.angle;
4748
+ const skew = config.skew === void 0 ? DEFAULT_DOT_SKEW : config.skew;
4749
+ validatePositive(radius, `${id}.radius`);
4750
+ validateNonNegative(gap, `${id}.gap`);
4751
+ validateAngle(angle, `${id}.angle`);
4752
+ validateSkew(skew, `${id}.skew`);
4753
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.dots;
4754
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = radius;
4755
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gap;
4756
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gap;
4757
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angle * DEGREES_TO_RADIANS3;
4758
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle1] = skew * DEGREES_TO_RADIANS3;
4759
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = 2;
4760
+ break;
4761
+ }
4762
+ default:
4763
+ throw new Error(`${id}.type is not a supported procedural pattern type`);
4764
+ }
4765
+ });
4766
+ return {
4767
+ data,
4768
+ width: data.length / PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL,
4769
+ height: 1,
4770
+ patternIndices
4771
+ };
4772
+ }
4773
+ function validatePositive(value, name) {
4774
+ if (!Number.isFinite(value) || value <= 0) {
4775
+ throw new Error(`${name} must be a finite number greater than 0`);
4776
+ }
4777
+ }
4778
+ function validateNonNegative(value, name) {
4779
+ if (!Number.isFinite(value) || value < 0) {
4780
+ throw new Error(`${name} must be a finite number greater than or equal to 0`);
4781
+ }
4782
+ }
4783
+ function validateAngle(value, name) {
4784
+ if (!Number.isFinite(value)) {
4785
+ throw new Error(`${name} must be a finite number`);
4786
+ }
4787
+ }
4788
+ function validateSkew(value, name) {
4789
+ if (!Number.isFinite(value) || value <= -90 || value >= 90) {
4790
+ throw new Error(`${name} must be a finite number greater than -90 and less than 90`);
4791
+ }
4792
+ }
4793
+
4090
4794
  // dist/fill-style/fill-style-extension.js
4795
+ function leastCommonMultiple(a, b) {
4796
+ if (!Number.isInteger(b) || b <= 0)
4797
+ return 0;
4798
+ if (a === 0)
4799
+ return b;
4800
+ let [x, y] = [a, b];
4801
+ while (y > 0)
4802
+ [x, y] = [y, x % y];
4803
+ return a / x * b;
4804
+ }
4091
4805
  var defaultProps4 = {
4092
4806
  fillPatternEnabled: true,
4093
4807
  fillPatternAtlas: {
4094
4808
  type: "image",
4095
4809
  value: null,
4096
- async: true,
4097
- parameters: { lodMaxClamp: 0 }
4810
+ async: true
4098
4811
  },
4099
4812
  fillPatternMapping: { type: "object", value: {}, async: true },
4100
4813
  fillPatternMask: true,
4814
+ fillPatternSizeUnits: "meters",
4101
4815
  getFillPattern: { type: "accessor", value: (d) => d.pattern },
4102
4816
  getFillPatternScale: { type: "accessor", value: 1 },
4103
- getFillPatternOffset: { type: "accessor", value: [0, 0] }
4817
+ getFillPatternOffset: { type: "accessor", value: [0, 0] },
4818
+ getFillPatternBackgroundColor: { type: "accessor", value: [0, 0, 0, 0] }
4104
4819
  };
4105
4820
  var FillStyleExtension = class extends LayerExtension {
4106
- constructor({ pattern = false } = {}) {
4107
- super({ pattern });
4821
+ constructor({ pattern = false, proceduralPattern = false } = {}) {
4822
+ super({ pattern: pattern || proceduralPattern, proceduralPattern });
4108
4823
  }
4109
4824
  isEnabled(layer) {
4110
4825
  return layer.getAttributeManager() !== null && !("pathTesselator" in layer.state);
@@ -4128,7 +4843,7 @@ var FillStyleExtension = class extends LayerExtension {
4128
4843
  size: 4,
4129
4844
  stepMode: "dynamic",
4130
4845
  accessor: "getFillPattern",
4131
- transform: extension.getPatternFrame.bind(this)
4846
+ transform: extension.opts.proceduralPattern ? extension.getProceduralPatternIndex.bind(this) : extension.getPatternFrame.bind(this)
4132
4847
  },
4133
4848
  fillPatternScales: {
4134
4849
  size: 1,
@@ -4140,6 +4855,13 @@ var FillStyleExtension = class extends LayerExtension {
4140
4855
  size: 2,
4141
4856
  stepMode: "dynamic",
4142
4857
  accessor: "getFillPatternOffset"
4858
+ },
4859
+ fillPatternBackgroundColors: {
4860
+ size: 4,
4861
+ type: "unorm8",
4862
+ stepMode: "dynamic",
4863
+ accessor: "getFillPatternBackgroundColor",
4864
+ defaultValue: [0, 0, 0, 255]
4143
4865
  }
4144
4866
  });
4145
4867
  }
@@ -4148,7 +4870,8 @@ var FillStyleExtension = class extends LayerExtension {
4148
4870
  data: new Uint8Array(4),
4149
4871
  width: 1,
4150
4872
  height: 1
4151
- })
4873
+ }),
4874
+ ...extension.opts.proceduralPattern && extension.createProceduralPatternTexture.call(this, {})
4152
4875
  });
4153
4876
  }
4154
4877
  updateState({ props, oldProps }, extension) {
@@ -4156,30 +4879,81 @@ var FillStyleExtension = class extends LayerExtension {
4156
4879
  return;
4157
4880
  }
4158
4881
  if (props.fillPatternMapping && props.fillPatternMapping !== oldProps.fillPatternMapping) {
4882
+ if (extension.opts.proceduralPattern && typeof props.fillPatternMapping !== "string") {
4883
+ const oldTexture = this.state.proceduralPatternTexture;
4884
+ this.setState(extension.createProceduralPatternTexture.call(this, props.fillPatternMapping));
4885
+ oldTexture == null ? void 0 : oldTexture.delete();
4886
+ }
4159
4887
  this.getAttributeManager().invalidate("getFillPattern");
4160
4888
  }
4889
+ if (props.fillPatternMapping !== oldProps.fillPatternMapping || props.getFillPatternScale !== oldProps.getFillPatternScale) {
4890
+ this.setState({ commonFrame: extension.getCommonFrame(props) });
4891
+ }
4161
4892
  }
4162
4893
  draw(params, extension) {
4163
4894
  if (!extension.isEnabled(this)) {
4164
4895
  return;
4165
4896
  }
4166
- const { fillPatternAtlas, fillPatternEnabled, fillPatternMask } = this.props;
4897
+ const { fillPatternAtlas, fillPatternEnabled, fillPatternMask, fillPatternSizeUnits } = this.props;
4167
4898
  const fillProps = {
4168
4899
  project: params.shaderModuleProps.project,
4169
4900
  fillPatternEnabled,
4170
4901
  fillPatternMask,
4171
- fillPatternTexture: fillPatternAtlas || this.state.emptyTexture
4902
+ fillPatternSizeUnits,
4903
+ procedural: extension.opts.proceduralPattern,
4904
+ fillPatternTexture: extension.opts.proceduralPattern ? this.state.proceduralPatternTexture || this.state.emptyTexture : fillPatternAtlas || this.state.emptyTexture,
4905
+ fillPatternCommonFrame: this.state.commonFrame
4172
4906
  };
4173
4907
  this.setShaderModuleProps({ fill: fillProps });
4174
4908
  }
4175
4909
  finalizeState() {
4176
4910
  const emptyTexture = this.state.emptyTexture;
4177
4911
  emptyTexture == null ? void 0 : emptyTexture.delete();
4912
+ const proceduralPatternTexture = this.state.proceduralPatternTexture;
4913
+ proceduralPatternTexture == null ? void 0 : proceduralPatternTexture.delete();
4178
4914
  }
4179
4915
  getPatternFrame(name) {
4180
4916
  const { fillPatternMapping } = this.getCurrentLayer().props;
4181
4917
  const def = fillPatternMapping && fillPatternMapping[name];
4182
- return def ? [def.x, def.y, def.width, def.height] : [0, 0, 0, 0];
4918
+ return def && "x" in def ? [def.x, def.y, def.width, def.height] : [0, 0, 0, 0];
4919
+ }
4920
+ /**
4921
+ * Returns common frame into which all patterns fit as integer multiples
4922
+ */
4923
+ getCommonFrame(props) {
4924
+ const { fillPatternMapping, getFillPatternScale: scale } = props;
4925
+ if (typeof scale !== "number" || !(scale > 0) || typeof fillPatternMapping !== "object") {
4926
+ return null;
4927
+ }
4928
+ let width = 0;
4929
+ let height = 0;
4930
+ for (const name in fillPatternMapping) {
4931
+ const frame = fillPatternMapping[name];
4932
+ if (!("width" in frame))
4933
+ return null;
4934
+ width = leastCommonMultiple(width, frame.width);
4935
+ height = leastCommonMultiple(height, frame.height);
4936
+ if (!width || !height)
4937
+ return null;
4938
+ }
4939
+ return width ? [scale * width, scale * height] : null;
4940
+ }
4941
+ getProceduralPatternIndex(name) {
4942
+ const patternIndices = this.state.proceduralPatternIndices;
4943
+ return [(patternIndices == null ? void 0 : patternIndices.get(name)) ?? 0, 0, 0, 0];
4944
+ }
4945
+ createProceduralPatternTexture(mapping) {
4946
+ const packedPatterns = packProceduralPatterns(mapping);
4947
+ return {
4948
+ proceduralPatternIndices: packedPatterns.patternIndices,
4949
+ proceduralPatternTexture: this.context.device.createTexture({
4950
+ data: packedPatterns.data,
4951
+ width: packedPatterns.width,
4952
+ height: packedPatterns.height,
4953
+ format: PROCEDURAL_PATTERN_TEXTURE_FORMAT,
4954
+ sampler: { minFilter: "nearest", magFilter: "nearest" }
4955
+ })
4956
+ };
4183
4957
  }
4184
4958
  };
4185
4959
  FillStyleExtension.defaultProps = defaultProps4;
@@ -4424,7 +5198,7 @@ var shader_module_default2 = {
4424
5198
  };
4425
5199
 
4426
5200
  // dist/collision-filter/collision-filter-effect.js
4427
- var import_core20 = require("@math.gl/core");
5201
+ var import_core19 = require("@math.gl/core");
4428
5202
 
4429
5203
  // dist/collision-filter/collision-filter-pass.js
4430
5204
  var CollisionFilterPass = class extends LayersPass {
@@ -4519,7 +5293,7 @@ var CollisionFilterEffect = class {
4519
5293
  const needsRender = viewportChanged || // If render info is new
4520
5294
  renderInfo === oldRenderInfo || // If sublayers have changed
4521
5295
  !deepEqual(oldRenderInfo.layers, renderInfo.layers, 1) || // If a sublayer's bounds have been updated
4522
- renderInfo.layerBounds.some((b, i) => !(0, import_core20.equals)(b, oldRenderInfo.layerBounds[i])) || // If a sublayer's isLoaded state has been updated
5296
+ renderInfo.layerBounds.some((b, i) => !(0, import_core19.equals)(b, oldRenderInfo.layerBounds[i])) || // If a sublayer's isLoaded state has been updated
4523
5297
  renderInfo.allLayersLoaded !== oldRenderInfo.allLayersLoaded || // Some prop is in transition
4524
5298
  renderInfo.layers.some((layer) => layer.props.transitions);
4525
5299
  this.channels[collisionGroup] = renderInfo;
@@ -4807,7 +5581,7 @@ var shader_module_default3 = {
4807
5581
  };
4808
5582
 
4809
5583
  // dist/mask/mask-effect.js
4810
- var import_core27 = require("@math.gl/core");
5584
+ var import_core26 = require("@math.gl/core");
4811
5585
 
4812
5586
  // dist/mask/mask-pass.js
4813
5587
  var MASK_BLENDING = {
@@ -5071,7 +5845,7 @@ var MaskEffect = class {
5071
5845
  this.lastViewport = viewport;
5072
5846
  const layerBounds = joinLayerBounds(channelInfo.layers, viewport);
5073
5847
  channelInfo.bounds = layerBounds && getRenderBounds(layerBounds, viewport);
5074
- if (maskChanged || !(0, import_core27.equals)(channelInfo.bounds, oldChannelInfo.bounds)) {
5848
+ if (maskChanged || !(0, import_core26.equals)(channelInfo.bounds, oldChannelInfo.bounds)) {
5075
5849
  const { maskPass, maskMap } = this;
5076
5850
  const maskViewport = layerBounds && makeViewport({
5077
5851
  bounds: channelInfo.bounds,