@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/dist.dev.js CHANGED
@@ -1176,10 +1176,10 @@ void main() {
1176
1176
  out[2] = a[2] * b;
1177
1177
  return out;
1178
1178
  }
1179
- function scaleAndAdd(out, a, b, scale2) {
1180
- out[0] = a[0] + b[0] * scale2;
1181
- out[1] = a[1] + b[1] * scale2;
1182
- out[2] = a[2] + b[2] * scale2;
1179
+ function scaleAndAdd(out, a, b, scale3) {
1180
+ out[0] = a[0] + b[0] * scale3;
1181
+ out[1] = a[1] + b[1] * scale3;
1182
+ out[2] = a[2] + b[2] * scale3;
1183
1183
  return out;
1184
1184
  }
1185
1185
  function distance(a, b) {
@@ -1216,13 +1216,13 @@ void main() {
1216
1216
  const x = a[0];
1217
1217
  const y = a[1];
1218
1218
  const z = a[2];
1219
- let len2 = x * x + y * y + z * z;
1220
- if (len2 > 0) {
1221
- len2 = 1 / Math.sqrt(len2);
1219
+ let len3 = x * x + y * y + z * z;
1220
+ if (len3 > 0) {
1221
+ len3 = 1 / Math.sqrt(len3);
1222
1222
  }
1223
- out[0] = a[0] * len2;
1224
- out[1] = a[1] * len2;
1225
- out[2] = a[2] * len2;
1223
+ out[0] = a[0] * len3;
1224
+ out[1] = a[1] * len3;
1225
+ out[2] = a[2] * len3;
1226
1226
  return out;
1227
1227
  }
1228
1228
  function dot(a, b) {
@@ -1283,14 +1283,14 @@ void main() {
1283
1283
  out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4;
1284
1284
  return out;
1285
1285
  }
1286
- function random(out, scale2) {
1287
- scale2 = scale2 === void 0 ? 1 : scale2;
1286
+ function random(out, scale3) {
1287
+ scale3 = scale3 === void 0 ? 1 : scale3;
1288
1288
  const r = RANDOM() * 2 * Math.PI;
1289
1289
  const z = RANDOM() * 2 - 1;
1290
- const zScale = Math.sqrt(1 - z * z) * scale2;
1290
+ const zScale = Math.sqrt(1 - z * z) * scale3;
1291
1291
  out[0] = Math.cos(r) * zScale;
1292
1292
  out[1] = Math.sin(r) * zScale;
1293
- out[2] = z * scale2;
1293
+ out[2] = z * scale3;
1294
1294
  return out;
1295
1295
  }
1296
1296
  function transformMat4(out, a, m) {
@@ -1449,6 +1449,372 @@ void main() {
1449
1449
  };
1450
1450
  })();
1451
1451
 
1452
+ // ../../node_modules/@math.gl/core/dist/gl-matrix/vec4.js
1453
+ var vec4_exports = {};
1454
+ __export(vec4_exports, {
1455
+ add: () => add2,
1456
+ ceil: () => ceil2,
1457
+ clone: () => clone2,
1458
+ copy: () => copy2,
1459
+ create: () => create2,
1460
+ cross: () => cross2,
1461
+ dist: () => dist2,
1462
+ distance: () => distance2,
1463
+ div: () => div2,
1464
+ divide: () => divide2,
1465
+ dot: () => dot2,
1466
+ equals: () => equals3,
1467
+ exactEquals: () => exactEquals2,
1468
+ floor: () => floor2,
1469
+ forEach: () => forEach2,
1470
+ fromValues: () => fromValues2,
1471
+ inverse: () => inverse2,
1472
+ len: () => len2,
1473
+ length: () => length2,
1474
+ lerp: () => lerp2,
1475
+ max: () => max2,
1476
+ min: () => min2,
1477
+ mul: () => mul2,
1478
+ multiply: () => multiply2,
1479
+ negate: () => negate2,
1480
+ normalize: () => normalize2,
1481
+ random: () => random2,
1482
+ round: () => round3,
1483
+ scale: () => scale2,
1484
+ scaleAndAdd: () => scaleAndAdd2,
1485
+ set: () => set2,
1486
+ sqrDist: () => sqrDist2,
1487
+ sqrLen: () => sqrLen2,
1488
+ squaredDistance: () => squaredDistance2,
1489
+ squaredLength: () => squaredLength2,
1490
+ str: () => str2,
1491
+ sub: () => sub2,
1492
+ subtract: () => subtract2,
1493
+ transformMat4: () => transformMat42,
1494
+ transformQuat: () => transformQuat2,
1495
+ zero: () => zero2
1496
+ });
1497
+ function create2() {
1498
+ const out = new ARRAY_TYPE(4);
1499
+ if (ARRAY_TYPE != Float32Array) {
1500
+ out[0] = 0;
1501
+ out[1] = 0;
1502
+ out[2] = 0;
1503
+ out[3] = 0;
1504
+ }
1505
+ return out;
1506
+ }
1507
+ function clone2(a) {
1508
+ const out = new ARRAY_TYPE(4);
1509
+ out[0] = a[0];
1510
+ out[1] = a[1];
1511
+ out[2] = a[2];
1512
+ out[3] = a[3];
1513
+ return out;
1514
+ }
1515
+ function fromValues2(x, y, z, w) {
1516
+ const out = new ARRAY_TYPE(4);
1517
+ out[0] = x;
1518
+ out[1] = y;
1519
+ out[2] = z;
1520
+ out[3] = w;
1521
+ return out;
1522
+ }
1523
+ function copy2(out, a) {
1524
+ out[0] = a[0];
1525
+ out[1] = a[1];
1526
+ out[2] = a[2];
1527
+ out[3] = a[3];
1528
+ return out;
1529
+ }
1530
+ function set2(out, x, y, z, w) {
1531
+ out[0] = x;
1532
+ out[1] = y;
1533
+ out[2] = z;
1534
+ out[3] = w;
1535
+ return out;
1536
+ }
1537
+ function add2(out, a, b) {
1538
+ out[0] = a[0] + b[0];
1539
+ out[1] = a[1] + b[1];
1540
+ out[2] = a[2] + b[2];
1541
+ out[3] = a[3] + b[3];
1542
+ return out;
1543
+ }
1544
+ function subtract2(out, a, b) {
1545
+ out[0] = a[0] - b[0];
1546
+ out[1] = a[1] - b[1];
1547
+ out[2] = a[2] - b[2];
1548
+ out[3] = a[3] - b[3];
1549
+ return out;
1550
+ }
1551
+ function multiply2(out, a, b) {
1552
+ out[0] = a[0] * b[0];
1553
+ out[1] = a[1] * b[1];
1554
+ out[2] = a[2] * b[2];
1555
+ out[3] = a[3] * b[3];
1556
+ return out;
1557
+ }
1558
+ function divide2(out, a, b) {
1559
+ out[0] = a[0] / b[0];
1560
+ out[1] = a[1] / b[1];
1561
+ out[2] = a[2] / b[2];
1562
+ out[3] = a[3] / b[3];
1563
+ return out;
1564
+ }
1565
+ function ceil2(out, a) {
1566
+ out[0] = Math.ceil(a[0]);
1567
+ out[1] = Math.ceil(a[1]);
1568
+ out[2] = Math.ceil(a[2]);
1569
+ out[3] = Math.ceil(a[3]);
1570
+ return out;
1571
+ }
1572
+ function floor2(out, a) {
1573
+ out[0] = Math.floor(a[0]);
1574
+ out[1] = Math.floor(a[1]);
1575
+ out[2] = Math.floor(a[2]);
1576
+ out[3] = Math.floor(a[3]);
1577
+ return out;
1578
+ }
1579
+ function min2(out, a, b) {
1580
+ out[0] = Math.min(a[0], b[0]);
1581
+ out[1] = Math.min(a[1], b[1]);
1582
+ out[2] = Math.min(a[2], b[2]);
1583
+ out[3] = Math.min(a[3], b[3]);
1584
+ return out;
1585
+ }
1586
+ function max2(out, a, b) {
1587
+ out[0] = Math.max(a[0], b[0]);
1588
+ out[1] = Math.max(a[1], b[1]);
1589
+ out[2] = Math.max(a[2], b[2]);
1590
+ out[3] = Math.max(a[3], b[3]);
1591
+ return out;
1592
+ }
1593
+ function round3(out, a) {
1594
+ out[0] = round(a[0]);
1595
+ out[1] = round(a[1]);
1596
+ out[2] = round(a[2]);
1597
+ out[3] = round(a[3]);
1598
+ return out;
1599
+ }
1600
+ function scale2(out, a, b) {
1601
+ out[0] = a[0] * b;
1602
+ out[1] = a[1] * b;
1603
+ out[2] = a[2] * b;
1604
+ out[3] = a[3] * b;
1605
+ return out;
1606
+ }
1607
+ function scaleAndAdd2(out, a, b, scale3) {
1608
+ out[0] = a[0] + b[0] * scale3;
1609
+ out[1] = a[1] + b[1] * scale3;
1610
+ out[2] = a[2] + b[2] * scale3;
1611
+ out[3] = a[3] + b[3] * scale3;
1612
+ return out;
1613
+ }
1614
+ function distance2(a, b) {
1615
+ const x = b[0] - a[0];
1616
+ const y = b[1] - a[1];
1617
+ const z = b[2] - a[2];
1618
+ const w = b[3] - a[3];
1619
+ return Math.sqrt(x * x + y * y + z * z + w * w);
1620
+ }
1621
+ function squaredDistance2(a, b) {
1622
+ const x = b[0] - a[0];
1623
+ const y = b[1] - a[1];
1624
+ const z = b[2] - a[2];
1625
+ const w = b[3] - a[3];
1626
+ return x * x + y * y + z * z + w * w;
1627
+ }
1628
+ function length2(a) {
1629
+ const x = a[0];
1630
+ const y = a[1];
1631
+ const z = a[2];
1632
+ const w = a[3];
1633
+ return Math.sqrt(x * x + y * y + z * z + w * w);
1634
+ }
1635
+ function squaredLength2(a) {
1636
+ const x = a[0];
1637
+ const y = a[1];
1638
+ const z = a[2];
1639
+ const w = a[3];
1640
+ return x * x + y * y + z * z + w * w;
1641
+ }
1642
+ function negate2(out, a) {
1643
+ out[0] = -a[0];
1644
+ out[1] = -a[1];
1645
+ out[2] = -a[2];
1646
+ out[3] = -a[3];
1647
+ return out;
1648
+ }
1649
+ function inverse2(out, a) {
1650
+ out[0] = 1 / a[0];
1651
+ out[1] = 1 / a[1];
1652
+ out[2] = 1 / a[2];
1653
+ out[3] = 1 / a[3];
1654
+ return out;
1655
+ }
1656
+ function normalize2(out, a) {
1657
+ const x = a[0];
1658
+ const y = a[1];
1659
+ const z = a[2];
1660
+ const w = a[3];
1661
+ let len3 = x * x + y * y + z * z + w * w;
1662
+ if (len3 > 0) {
1663
+ len3 = 1 / Math.sqrt(len3);
1664
+ }
1665
+ out[0] = x * len3;
1666
+ out[1] = y * len3;
1667
+ out[2] = z * len3;
1668
+ out[3] = w * len3;
1669
+ return out;
1670
+ }
1671
+ function dot2(a, b) {
1672
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
1673
+ }
1674
+ function cross2(out, u, v, w) {
1675
+ const A = v[0] * w[1] - v[1] * w[0];
1676
+ const B = v[0] * w[2] - v[2] * w[0];
1677
+ const C = v[0] * w[3] - v[3] * w[0];
1678
+ const D = v[1] * w[2] - v[2] * w[1];
1679
+ const E = v[1] * w[3] - v[3] * w[1];
1680
+ const F = v[2] * w[3] - v[3] * w[2];
1681
+ const G = u[0];
1682
+ const H = u[1];
1683
+ const I = u[2];
1684
+ const J = u[3];
1685
+ out[0] = H * F - I * E + J * D;
1686
+ out[1] = -(G * F) + I * C - J * B;
1687
+ out[2] = G * E - H * C + J * A;
1688
+ out[3] = -(G * D) + H * B - I * A;
1689
+ return out;
1690
+ }
1691
+ function lerp2(out, a, b, t) {
1692
+ const ax = a[0];
1693
+ const ay = a[1];
1694
+ const az = a[2];
1695
+ const aw = a[3];
1696
+ out[0] = ax + t * (b[0] - ax);
1697
+ out[1] = ay + t * (b[1] - ay);
1698
+ out[2] = az + t * (b[2] - az);
1699
+ out[3] = aw + t * (b[3] - aw);
1700
+ return out;
1701
+ }
1702
+ function random2(out, scale3) {
1703
+ scale3 = scale3 === void 0 ? 1 : scale3;
1704
+ let v1;
1705
+ let v2;
1706
+ let v3;
1707
+ let v4;
1708
+ let s1;
1709
+ let s2;
1710
+ do {
1711
+ v1 = RANDOM() * 2 - 1;
1712
+ v2 = RANDOM() * 2 - 1;
1713
+ s1 = v1 * v1 + v2 * v2;
1714
+ } while (s1 >= 1);
1715
+ do {
1716
+ v3 = RANDOM() * 2 - 1;
1717
+ v4 = RANDOM() * 2 - 1;
1718
+ s2 = v3 * v3 + v4 * v4;
1719
+ } while (s2 >= 1);
1720
+ const d = Math.sqrt((1 - s1) / s2);
1721
+ out[0] = scale3 * v1;
1722
+ out[1] = scale3 * v2;
1723
+ out[2] = scale3 * v3 * d;
1724
+ out[3] = scale3 * v4 * d;
1725
+ return out;
1726
+ }
1727
+ function transformMat42(out, a, m) {
1728
+ const x = a[0];
1729
+ const y = a[1];
1730
+ const z = a[2];
1731
+ const w = a[3];
1732
+ out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
1733
+ out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
1734
+ out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
1735
+ out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
1736
+ return out;
1737
+ }
1738
+ function transformQuat2(out, a, q) {
1739
+ const x = a[0];
1740
+ const y = a[1];
1741
+ const z = a[2];
1742
+ const qx = q[0];
1743
+ const qy = q[1];
1744
+ const qz = q[2];
1745
+ const qw = q[3];
1746
+ const ix = qw * x + qy * z - qz * y;
1747
+ const iy = qw * y + qz * x - qx * z;
1748
+ const iz = qw * z + qx * y - qy * x;
1749
+ const iw = -qx * x - qy * y - qz * z;
1750
+ out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
1751
+ out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
1752
+ out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
1753
+ out[3] = a[3];
1754
+ return out;
1755
+ }
1756
+ function zero2(out) {
1757
+ out[0] = 0;
1758
+ out[1] = 0;
1759
+ out[2] = 0;
1760
+ out[3] = 0;
1761
+ return out;
1762
+ }
1763
+ function str2(a) {
1764
+ return `vec4(${a[0]}, ${a[1]}, ${a[2]}, ${a[3]})`;
1765
+ }
1766
+ function exactEquals2(a, b) {
1767
+ return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
1768
+ }
1769
+ function equals3(a, b) {
1770
+ const a0 = a[0];
1771
+ const a1 = a[1];
1772
+ const a2 = a[2];
1773
+ const a3 = a[3];
1774
+ const b0 = b[0];
1775
+ const b1 = b[1];
1776
+ const b2 = b[2];
1777
+ const b3 = b[3];
1778
+ return Math.abs(a0 - b0) <= EPSILON * Math.max(1, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1, Math.abs(a3), Math.abs(b3));
1779
+ }
1780
+ var sub2 = subtract2;
1781
+ var mul2 = multiply2;
1782
+ var div2 = divide2;
1783
+ var dist2 = distance2;
1784
+ var sqrDist2 = squaredDistance2;
1785
+ var len2 = length2;
1786
+ var sqrLen2 = squaredLength2;
1787
+ var forEach2 = (function() {
1788
+ const vec = create2();
1789
+ return function(a, stride, offset, count, fn, arg) {
1790
+ let i;
1791
+ let l;
1792
+ if (!stride) {
1793
+ stride = 4;
1794
+ }
1795
+ if (!offset) {
1796
+ offset = 0;
1797
+ }
1798
+ if (count) {
1799
+ l = Math.min(count * stride + offset, a.length);
1800
+ } else {
1801
+ l = a.length;
1802
+ }
1803
+ for (i = offset; i < l; i += stride) {
1804
+ vec[0] = a[i];
1805
+ vec[1] = a[i + 1];
1806
+ vec[2] = a[i + 2];
1807
+ vec[3] = a[i + 3];
1808
+ fn(vec, vec, arg);
1809
+ a[i] = vec[0];
1810
+ a[i + 1] = vec[1];
1811
+ a[i + 2] = vec[2];
1812
+ a[i + 3] = vec[3];
1813
+ }
1814
+ return a;
1815
+ };
1816
+ })();
1817
+
1452
1818
  // ../../node_modules/@luma.gl/shadertools/dist/modules/math/fp64/fp64-utils.js
1453
1819
  function fp64ify(a, out = [], startIndex = 0) {
1454
1820
  const hiPart = Math.fround(a);
@@ -3857,14 +4223,14 @@ vec4 project_position_to_clipspace(
3857
4223
  var getMemoizedUniforms = (0, import_core5._memoize)(calculateUniforms);
3858
4224
  function getUniforms2(opts) {
3859
4225
  if (opts && "viewport" in opts) {
3860
- const { viewProjectionMatrix, scale: scale2 } = opts.viewport;
3861
- return getMemoizedUniforms({ viewProjectionMatrix, scale: scale2 });
4226
+ const { viewProjectionMatrix, scale: scale3 } = opts.viewport;
4227
+ return getMemoizedUniforms({ viewProjectionMatrix, scale: scale3 });
3862
4228
  }
3863
4229
  return {};
3864
4230
  }
3865
4231
  function calculateUniforms({
3866
4232
  viewProjectionMatrix,
3867
- scale: scale2
4233
+ scale: scale3
3868
4234
  }) {
3869
4235
  const glViewProjectionMatrixFP64 = fp64ifyMatrix42(viewProjectionMatrix);
3870
4236
  const viewProjectionMatrix64High = new Float32Array(16);
@@ -3878,7 +4244,7 @@ vec4 project_position_to_clipspace(
3878
4244
  }
3879
4245
  }
3880
4246
  return {
3881
- scale: fp64ify2(scale2),
4247
+ scale: fp64ify2(scale3),
3882
4248
  viewProjectionMatrix: [...viewProjectionMatrix64High],
3883
4249
  viewProjectionMatrix64Low: [...viewProjectionMatrix64Low]
3884
4250
  };
@@ -3911,27 +4277,121 @@ vec4 project_position_to_clipspace(
3911
4277
  "vs:#decl": `
3912
4278
  in vec2 instanceDashArrays;
3913
4279
  #ifdef HIGH_PRECISION_DASH
3914
- in float instanceDashOffsets;
4280
+ // [distance from the start of the path, total length of the path], in common space.
4281
+ in vec2 instanceDashOffsets;
3915
4282
  #endif
3916
4283
  out vec2 vDashArray;
3917
4284
  out float vDashOffset;
4285
+ out float vDashPathLength;
4286
+
4287
+ // Also declared in the fragment stage. The vertex stage needs dashAlignMode so that it can
4288
+ // reduce the dash phase modulo the same period the fragment stage will test against, and
4289
+ // dashUnits to scale the dash array into that period's units.
4290
+ layout(std140) uniform pathStyleUniforms {
4291
+ float dashAlignMode;
4292
+ bool dashGapPickable;
4293
+ highp int dashUnits;
4294
+ } pathStyle;
3918
4295
  `,
3919
4296
  "vs:#main-end": `
3920
- vDashArray = instanceDashArrays;
4297
+ // How many screen pixels one unit of vPathPosition.y covers. The two extrusion branches of
4298
+ // the path layer leave this in different units, and both differences have to be undone here
4299
+ // for a billboarded path to dash like a flat one.
4300
+ //
4301
+ // Flat paths extrude in common space, so \`width\` is in common units and a unit of
4302
+ // vPathPosition.y is width.x * project.scale pixels.
4303
+ //
4304
+ // Billboarded paths extrude in clip space, so \`width\` is already in pixels at this point -
4305
+ // the clip-space conversion happens inside getLineJoinOffset. That conversion also folds in
4306
+ // project.focalDistance, which scales the half-width the segment delta is divided by but not
4307
+ // the delta itself, so a unit of vPathPosition.y ends up focalDistance pixels wider than the
4308
+ // stroke it is supposed to be relative to.
4309
+ float dashWidthPixels = path.billboard
4310
+ ? width.x * project.focalDistance
4311
+ : width.x * project.scale;
4312
+
4313
+ // The actual half stroke width on screen, which is what 'widths' is relative to. It differs
4314
+ // from dashWidthPixels by exactly the spurious focalDistance above, so expressing 'widths'
4315
+ // as a ratio of the two corrects the billboard case onto the flat one - getDashArray is
4316
+ // documented relative to the stroke, and flat paths already honor that.
4317
+ float strokeHalfWidthPixels = path.billboard ? width.x : width.x * project.scale;
4318
+
4319
+ // Everything reduces to "how many screen pixels is one dash unit", divided through by the
4320
+ // pixels per unit of vPathPosition.y, so the fragment shader needs no notion of units at all.
4321
+ // Keep the cases in sync with DASH_UNITS in path-style-extension.ts.
4322
+ float dashUnitPixels = strokeHalfWidthPixels;
4323
+ if (pathStyle.dashUnits == 1) {
4324
+ dashUnitPixels = 1.0;
4325
+ } else if (pathStyle.dashUnits == 2) {
4326
+ dashUnitPixels = project_size_to_pixel(1.0);
4327
+ } else if (pathStyle.dashUnits == 3) {
4328
+ dashUnitPixels = project.scale;
4329
+ }
4330
+ vDashArray = instanceDashArrays * (dashUnitPixels / dashWidthPixels);
4331
+
3921
4332
  #ifdef HIGH_PRECISION_DASH
3922
- vDashOffset = instanceDashOffsets / width.x;
4333
+ // instanceDashOffsets accumulates common-space distance on the CPU. Convert it to pixels, then
4334
+ // divide by the pixel half-width so both extrusion branches use the same dash coordinate.
4335
+ vec2 dashOffsetAndLength = (instanceDashOffsets * project.scale) / dashWidthPixels;
4336
+ vDashPathLength = dashOffsetAndLength.y;
4337
+
4338
+ // Reduce the phase into the first period here rather than in the fragment shader. On a long
4339
+ // path at high zoom the raw offset reaches into the millions, and adding the much smaller
4340
+ // vPathPosition.y to it in fp32 loses the latter entirely, freezing the pattern mid-segment.
4341
+ // The dash function is periodic, so this discards nothing - but it has to be reduced modulo
4342
+ // whichever period the fragment stage will actually test against, hence dashAlignMode here.
4343
+ float dashUnitLength = vDashArray.x + vDashArray.y;
4344
+ float dashPeriod = pathStyle.dashAlignMode == 0.0
4345
+ ? dashUnitLength
4346
+ : vDashPathLength / max(round(vDashPathLength / max(dashUnitLength, 0.0001)), 1.0);
4347
+ vDashOffset = dashPeriod > 0.0 ? mod(dashOffsetAndLength.x, dashPeriod) : 0.0;
3923
4348
  #else
3924
4349
  vDashOffset = 0.0;
4350
+ vDashPathLength = 0.0;
3925
4351
  #endif
3926
4352
  `,
3927
4353
  "fs:#decl": `
3928
4354
  layout(std140) uniform pathStyleUniforms {
3929
4355
  float dashAlignMode;
3930
4356
  bool dashGapPickable;
4357
+ highp int dashUnits;
3931
4358
  } pathStyle;
3932
4359
 
3933
4360
  in vec2 vDashArray;
3934
4361
  in float vDashOffset;
4362
+ in float vDashPathLength;
4363
+
4364
+ // Integral of the dash square wave from 0 to position, i.e. how much solid stroke lies before it.
4365
+ float dashPatternIntegral(float position, float solidLength, float unitLength) {
4366
+ return floor(position / unitLength) * solidLength +
4367
+ min(mod(position, unitLength), solidLength);
4368
+ }
4369
+
4370
+ // Fraction of the filter-width interval centered on position that is covered by solid stroke.
4371
+ //
4372
+ // Testing the dash pattern with a single comparison per fragment aliases as soon as one
4373
+ // period approaches one pixel: the stroke breaks into moire or, when the phase happens to
4374
+ // land inside a dash, reads as solid. Integrating the wave over the fragment footprint
4375
+ // instead is the closed form of what a mipmapped dash texture approximates, and it degrades
4376
+ // the way one wants - once a period drops below a pixel the result converges on the duty
4377
+ // cycle solidLength / unitLength, so the stroke fades to a uniformly lighter line.
4378
+ //
4379
+ // position is reduced into the first period first. The integral satisfies
4380
+ // F(position + periodIndex * unitLength) = F(position) + periodIndex * solidLength,
4381
+ // so the same periodIndex cancels out of the
4382
+ // difference below, and keeping the arguments small avoids subtracting two large nearly
4383
+ // equal numbers in fp32 on long paths.
4384
+ float dashPatternCoverage(
4385
+ float position,
4386
+ float solidLength,
4387
+ float unitLength,
4388
+ float filterWidth
4389
+ ) {
4390
+ float halfFilter = 0.5 * filterWidth;
4391
+ float reducedPosition = mod(position, unitLength);
4392
+ return (dashPatternIntegral(reducedPosition + halfFilter, solidLength, unitLength) -
4393
+ dashPatternIntegral(reducedPosition - halfFilter, solidLength, unitLength)) / filterWidth;
4394
+ }
3935
4395
  `,
3936
4396
  // if given position is in the gap part of the dashed line
3937
4397
  // dashArray.x: solid stroke length, relative to width
@@ -3942,41 +4402,130 @@ in float vDashOffset;
3942
4402
  // 1 - stretch to fit, draw half dash at each end for nicer joints
3943
4403
  // o-- ---- ---- ---- --o-- --o-- ---- --o
3944
4404
  "fs:#main-start": `
4405
+ float dashCoverage = 1.0;
4406
+ bool shouldDiscardDash = false;
4407
+ bool inRoundedDashGap = false;
4408
+ float roundedDashResolvedCoverage = 1.0;
4409
+ float roundedDashSubPixelBlend = 0.0;
4410
+ float roundedDashDutyCycle = 1.0;
4411
+
3945
4412
  float solidLength = vDashArray.x;
3946
4413
  float gapLength = vDashArray.y;
3947
4414
  float unitLength = solidLength + gapLength;
3948
4415
 
3949
- float offset;
3950
-
3951
- if (unitLength > 0.0) {
4416
+ if (unitLength > 0.0 && gapLength > 0.0) {
4417
+ float offset;
3952
4418
  if (pathStyle.dashAlignMode == 0.0) {
3953
4419
  offset = vDashOffset;
3954
4420
  } else {
3955
- unitLength = vPathLength / round(vPathLength / unitLength);
4421
+ // Justified: stretch the period so a whole number of them spans the run, and start
4422
+ // half a dash in so both ends finish on a joint. Rounding up to at least one period
4423
+ // matters - a run shorter than half a period used to round to zero, which made
4424
+ // unitLength infinite and rendered it solid.
4425
+ #ifdef HIGH_PRECISION_DASH
4426
+ // Justify across the entire path rather than each segment separately, so the gaps stay
4427
+ // even instead of being stretched by a different amount on every segment. vDashOffset
4428
+ // already carries the distance to the start of this segment, reduced modulo this same
4429
+ // period in the vertex shader.
4430
+ unitLength = vDashPathLength / max(round(vDashPathLength / unitLength), 1.0);
4431
+ #else
4432
+ unitLength = vPathLength / max(round(vPathLength / unitLength), 1.0);
4433
+ #endif
4434
+ // A short segment or path can make the fitted period shorter than the requested dash.
4435
+ // Bound the solid interval to that period so coverage and duty cycle remain valid.
4436
+ solidLength = min(solidLength, unitLength);
3956
4437
  offset = solidLength / 2.0;
4438
+ #ifdef HIGH_PRECISION_DASH
4439
+ offset += vDashOffset;
4440
+ #endif
3957
4441
  }
3958
4442
 
3959
- float unitOffset = mod(vPathPosition.y + offset, unitLength);
4443
+ float alongPath = vPathPosition.y + offset;
4444
+ float unitOffset = mod(alongPath, unitLength);
4445
+ float filterWidth = max(fwidth(alongPath), 0.0001);
3960
4446
 
3961
- if (gapLength > 0.0 && unitOffset > solidLength) {
3962
- if (path.capType <= 0.5) {
3963
- if (!(pathStyle.dashGapPickable && bool(picking.isActive))) {
3964
- discard;
3965
- }
3966
- } else {
3967
- // caps are rounded, test the distance to solid ends
3968
- float distToEnd = length(vec2(
4447
+ // Picking stays a hard in-or-out test. A blended picking colour decodes to the wrong
4448
+ // index, and dashGapPickable is defined in terms of whole gaps rather than coverage.
4449
+ if (bool(picking.isActive)) {
4450
+ bool inGap = unitOffset > solidLength;
4451
+ // Only measure the rounded end-cap distance after the hard interval test has found a
4452
+ // gap. Inside a solid interval the longitudinal distance is negative; taking its
4453
+ // vector length would turn it positive and incorrectly discard the middle of a dash.
4454
+ if (inGap && path.capType > 0.5) {
4455
+ inGap = length(vec2(
3969
4456
  min(unitOffset - solidLength, unitLength - unitOffset),
3970
4457
  vPathPosition.x
3971
- ));
3972
- if (distToEnd > 1.0) {
3973
- if (!(pathStyle.dashGapPickable && bool(picking.isActive))) {
3974
- discard;
3975
- }
3976
- }
4458
+ )) > 1.0;
3977
4459
  }
4460
+ if (inGap && !pathStyle.dashGapPickable) {
4461
+ shouldDiscardDash = true;
4462
+ }
4463
+ } else if (path.capType <= 0.5) {
4464
+ dashCoverage = dashPatternCoverage(alongPath, solidLength, unitLength, filterWidth);
4465
+ } else {
4466
+ // Rounded caps: the dash end is an arc, so resolve the 2D distance to the nearer solid
4467
+ // end rather than the 1D position along the path. Only filter fragments in the gap;
4468
+ // PathLayer already antialiases the ordinary sides of the solid stroke.
4469
+ float distanceAlongGap = min(unitOffset - solidLength, unitLength - unitOffset);
4470
+ // Clamp before taking the derivative so the distance stays continuous across the
4471
+ // wrapped period boundary. Evaluate fwidth for every fragment in the quad; derivatives
4472
+ // are undefined inside the non-uniform gap branch below.
4473
+ float distanceToEnd = length(vec2(max(distanceAlongGap, 0.0), vPathPosition.x));
4474
+ float capEdgePixels = (1.0 - distanceToEnd) / max(fwidth(distanceToEnd), 1e-6);
4475
+ if (distanceAlongGap > 0.0) {
4476
+ inRoundedDashGap = true;
4477
+ roundedDashResolvedCoverage = smoothedge(0.0, capEdgePixels);
4478
+ dashCoverage = roundedDashResolvedCoverage;
4479
+ }
4480
+ // That smoothstep resolves one dash end at a time, so it stops meaning anything once a
4481
+ // whole period fits inside the filter footprint. Start fading only at that boundary;
4482
+ // blending resolvable periods would attenuate the solid body of every rounded dash.
4483
+ float subPixelBlend = smoothstep(unitLength, 2.0 * unitLength, filterWidth);
4484
+ roundedDashSubPixelBlend = subPixelBlend;
4485
+ // At sub-pixel scale, preserve the area of the repeated capsule rather than falling
4486
+ // back to the rectangular duty cycle. Each scanline contains the solid body plus the
4487
+ // two circular cap intrusions, capped when neighboring caps overlap across the gap.
4488
+ float boundedSolidLength = min(solidLength, unitLength);
4489
+ float effectiveGap = max(unitLength - boundedSolidLength, 0.0);
4490
+ float capSpan = 2.0 * sqrt(max(1.0 - vPathPosition.x * vPathPosition.x, 0.0));
4491
+ float roundedDutyCycle = clamp(
4492
+ (boundedSolidLength + min(effectiveGap, capSpan)) / unitLength,
4493
+ 0.0,
4494
+ 1.0
4495
+ );
4496
+ roundedDashDutyCycle = roundedDutyCycle;
4497
+ dashCoverage = mix(dashCoverage, roundedDutyCycle, subPixelBlend);
3978
4498
  }
4499
+
4500
+ dashCoverage = clamp(dashCoverage, 0.0, 1.0);
4501
+ // Fully transparent fragments would still write depth and occlude whatever is behind.
4502
+ shouldDiscardDash = shouldDiscardDash || dashCoverage <= 0.0;
4503
+ }
4504
+ `,
4505
+ "fs:#main-end": `
4506
+ // PathLayer computes analytic-edge derivatives in its fragment body. A discard in
4507
+ // #main-start can remove helper invocations and make those derivatives undefined, so all
4508
+ // dash-related termination is deferred until the layer has completed that work.
4509
+ #ifdef ANTIALIASING
4510
+ if (inRoundedDashGap) {
4511
+ // The rounded dash cap and PathLayer silhouette are two geometric constraints on the
4512
+ // same fragment. Intersect their coverage instead of multiplying two edge ramps; at the
4513
+ // cap/body shoulder both ramps are half covered and multiplication would create a dark
4514
+ // quarter-covered notch. Keep the sub-pixel duty cycle separable from the path silhouette.
4515
+ float pathCoverage = smoothedge(0.0, edgePixels);
4516
+ float resolvedCapMultiplier =
4517
+ min(pathCoverage, roundedDashResolvedCoverage) / max(pathCoverage, 1e-6);
4518
+ dashCoverage = mix(
4519
+ resolvedCapMultiplier,
4520
+ roundedDashDutyCycle,
4521
+ roundedDashSubPixelBlend
4522
+ );
4523
+ }
4524
+ #endif
4525
+ if (shouldDiscardDash) {
4526
+ discard;
3979
4527
  }
4528
+ fragColor.a *= dashCoverage;
3980
4529
  `
3981
4530
  }
3982
4531
  };
@@ -4198,13 +4747,42 @@ in float instanceOffsets;
4198
4747
  vPathPosition.x = (vPathPosition.x + offsetDir) * offsetWidth - offsetDir;
4199
4748
  vPathPosition.y *= offsetWidth;
4200
4749
  vPathLength *= offsetWidth;
4750
+ #ifdef DASH_ENABLED
4751
+ // DECKGL_FILTER_SIZE above widened the stroke by offsetWidth, so these rescalings restore
4752
+ // units of the original half-width. The dash block merges ahead of this one.
4753
+ if (pathStyle.dashUnits != 0) {
4754
+ // Absolute dash units must not inherit the artificial width used to build offset
4755
+ // geometry. Relative 'widths' units intentionally remain tied to the original stroke.
4756
+ vDashArray *= offsetWidth;
4757
+ }
4758
+ vPathBounds *= offsetWidth;
4759
+ #ifdef HIGH_PRECISION_DASH
4760
+ vDashPathLength *= offsetWidth;
4761
+ // The dash block reduced the CPU path offset modulo a period expressed in widened-width
4762
+ // units. Multiplying that remainder cannot recover the original phase after wrapping, so
4763
+ // redo the reduction with both the restored path offset and restored justified period.
4764
+ float restoredDashUnitLength = vDashArray.x + vDashArray.y;
4765
+ float restoredDashPeriod = pathStyle.dashAlignMode == 0.0
4766
+ ? restoredDashUnitLength
4767
+ : vDashPathLength /
4768
+ max(round(vDashPathLength / max(restoredDashUnitLength, 0.0001)), 1.0);
4769
+ vDashOffset = restoredDashPeriod > 0.0
4770
+ ? mod(dashOffsetAndLength.x * offsetWidth, restoredDashPeriod)
4771
+ : 0.0;
4772
+ #else
4773
+ vDashOffset *= offsetWidth;
4774
+ #endif
4775
+ #endif
4201
4776
  `,
4202
- "fs:#main-start": `
4203
- float isInside;
4204
- isInside = step(-1.0, vPathPosition.x) * step(vPathPosition.x, 1.0);
4205
- if (isInside == 0.0) {
4777
+ "fs:#main-end": `
4778
+ #ifndef ANTIALIASING
4779
+ // With analytic antialiasing, PathLayer evaluates this boundary using the remapped
4780
+ // vPathPosition and retains the complete centered coverage ramp. The hard clip remains for
4781
+ // the original non-AA path.
4782
+ if (abs(vPathPosition.x) > 1.0) {
4206
4783
  discard;
4207
4784
  }
4785
+ #endif
4208
4786
  `
4209
4787
  }
4210
4788
  };
@@ -4214,15 +4792,73 @@ in float instanceOffsets;
4214
4792
  getDashArray: { type: "accessor", value: [0, 0] },
4215
4793
  getOffset: { type: "accessor", value: 0 },
4216
4794
  dashJustified: false,
4217
- dashGapPickable: false
4795
+ dashGapPickable: false,
4796
+ dashUnits: "widths"
4218
4797
  };
4798
+ var DASH_UNITS = {
4799
+ widths: 0,
4800
+ pixels: 1,
4801
+ meters: 2,
4802
+ common: 3
4803
+ };
4804
+ var PATH_STYLE_ATTRIBUTES = ["instanceDashArrays", "instanceDashOffsets", "instanceOffsets"];
4805
+ function dashMetricsProjectionPropsChanged({
4806
+ props,
4807
+ oldProps
4808
+ }) {
4809
+ return !(0, import_core7._deepEqual)(props.modelMatrix, oldProps.modelMatrix, 2) || props.coordinateSystem !== oldProps.coordinateSystem || !(0, import_core7._deepEqual)(props.coordinateOrigin, oldProps.coordinateOrigin, 1);
4810
+ }
4811
+ function projectRenderedPathPosition(layer, position, projectUniforms) {
4812
+ const viewport = layer.context.viewport;
4813
+ if (!(viewport instanceof import_core7.WebMercatorViewport) || viewport.zoom < 12) {
4814
+ return layer.projectPosition(position, { autoOffset: false });
4815
+ }
4816
+ const positionWorld = vec4_exports.transformMat4(
4817
+ [],
4818
+ [position[0], position[1], position[2] || 0, 1],
4819
+ projectUniforms.modelMatrix
4820
+ );
4821
+ const coordinateSystem = layer.props.coordinateSystem === "default" ? "lnglat" : layer.props.coordinateSystem;
4822
+ if (coordinateSystem === "lnglat") {
4823
+ if (Math.abs(positionWorld[1] - projectUniforms.coordinateOrigin[1]) > 0.25) {
4824
+ let longitude = positionWorld[0];
4825
+ if (projectUniforms.wrapLongitude) {
4826
+ longitude = ((longitude + 180) % 360 + 360) % 360 - 180;
4827
+ }
4828
+ const commonPosition = viewport.projectPosition([longitude, positionWorld[1], 0]);
4829
+ return [
4830
+ commonPosition[0] - projectUniforms.commonOrigin[0],
4831
+ commonPosition[1] - projectUniforms.commonOrigin[1],
4832
+ positionWorld[2] * projectUniforms.commonUnitsPerMeter[2]
4833
+ ];
4834
+ }
4835
+ vec3_exports.sub(positionWorld, positionWorld, projectUniforms.coordinateOrigin);
4836
+ } else if (coordinateSystem === "cartesian") {
4837
+ vec3_exports.sub(positionWorld, positionWorld, projectUniforms.coordinateOrigin);
4838
+ }
4839
+ const deltaLatitude = positionWorld[1];
4840
+ const scale3 = vec3_exports.scaleAndAdd(
4841
+ [],
4842
+ projectUniforms.commonUnitsPerWorldUnit,
4843
+ projectUniforms.commonUnitsPerWorldUnit2,
4844
+ deltaLatitude
4845
+ );
4846
+ return vec3_exports.multiply([], positionWorld, scale3);
4847
+ }
4219
4848
  var PathStyleExtension = class extends import_core7.LayerExtension {
4220
4849
  constructor({
4221
4850
  dash = false,
4222
4851
  offset = false,
4852
+ dashMode,
4223
4853
  highPrecisionDash = false
4224
4854
  } = {}) {
4225
- super({ dash: dash || highPrecisionDash, offset, highPrecisionDash });
4855
+ const resolvedDashMode = dashMode ?? (highPrecisionDash ? "path" : "segment");
4856
+ super({
4857
+ dash: dash || highPrecisionDash || dashMode !== void 0,
4858
+ offset,
4859
+ dashMode: resolvedDashMode,
4860
+ highPrecisionDash: resolvedDashMode === "path"
4861
+ });
4226
4862
  }
4227
4863
  getLayerType(layer) {
4228
4864
  if ("pathTesselator" in layer.state) {
@@ -4237,6 +4873,59 @@ in float instanceOffsets;
4237
4873
  }
4238
4874
  return null;
4239
4875
  }
4876
+ synchronizeAttributes(layer) {
4877
+ const attributeManager = layer.getAttributeManager();
4878
+ const layerType = this.getLayerType(layer);
4879
+ if (!attributeManager || !layerType) {
4880
+ return false;
4881
+ }
4882
+ const attributes = attributeManager.getAttributes();
4883
+ const desiredAttributes = /* @__PURE__ */ new Set();
4884
+ if (this.opts.dash) {
4885
+ desiredAttributes.add("instanceDashArrays");
4886
+ }
4887
+ if (layerType === "path" && this.opts.dash && this.opts.dashMode === "path") {
4888
+ desiredAttributes.add("instanceDashOffsets");
4889
+ }
4890
+ if (layerType === "path" && this.opts.offset) {
4891
+ desiredAttributes.add("instanceOffsets");
4892
+ }
4893
+ let topologyChanged = false;
4894
+ for (const attributeName of PATH_STYLE_ATTRIBUTES) {
4895
+ if (attributes[attributeName] && !desiredAttributes.has(attributeName)) {
4896
+ attributeManager.remove([attributeName]);
4897
+ topologyChanged = true;
4898
+ }
4899
+ }
4900
+ if (desiredAttributes.has("instanceDashArrays") && !attributes.instanceDashArrays) {
4901
+ attributeManager.addInstanced({
4902
+ instanceDashArrays: { size: 2, accessor: "getDashArray" }
4903
+ });
4904
+ topologyChanged = true;
4905
+ }
4906
+ if (desiredAttributes.has("instanceDashOffsets") && !attributes.instanceDashOffsets) {
4907
+ attributeManager.addInstanced({
4908
+ instanceDashOffsets: {
4909
+ // [distance from the start of the path, total length of the path]
4910
+ size: 2,
4911
+ // Recalculate from rendered geometry when getPath changes. A binary getPath buffer
4912
+ // contains positions, not dash metrics, so keep it as an update trigger rather than
4913
+ // binding it directly to this attribute.
4914
+ accessor: ["getPath"],
4915
+ // eslint-disable-next-line @typescript-eslint/unbound-method
4916
+ update: this.calculateDashMetrics
4917
+ }
4918
+ });
4919
+ topologyChanged = true;
4920
+ }
4921
+ if (desiredAttributes.has("instanceOffsets") && !attributes.instanceOffsets) {
4922
+ attributeManager.addInstanced({
4923
+ instanceOffsets: { size: 1, accessor: "getOffset" }
4924
+ });
4925
+ topologyChanged = true;
4926
+ }
4927
+ return topologyChanged;
4928
+ }
4240
4929
  isEnabled(layer) {
4241
4930
  return this.getLayerType(layer) !== null;
4242
4931
  }
@@ -4263,57 +4952,54 @@ in float instanceOffsets;
4263
4952
  const defines = {};
4264
4953
  if (extension.opts.dash) {
4265
4954
  result = (0, import_core7._mergeShaders)(result, dashShaders);
4266
- if (extension.opts.highPrecisionDash) {
4955
+ defines.DASH_ENABLED = true;
4956
+ if (extension.opts.dashMode === "path") {
4267
4957
  defines.HIGH_PRECISION_DASH = true;
4268
4958
  }
4269
4959
  }
4270
4960
  if (extension.opts.offset) {
4271
4961
  result = (0, import_core7._mergeShaders)(result, offsetShaders);
4962
+ defines.PATH_STYLE_OFFSET = true;
4272
4963
  }
4273
4964
  const { inject: inject6 } = result;
4274
4965
  const pathStyle = {
4275
4966
  name: "pathStyle",
4276
- inject: inject6,
4277
- uniformTypes: {
4278
- dashAlignMode: "f32",
4279
- dashGapPickable: "i32"
4280
- }
4967
+ inject: inject6
4281
4968
  };
4969
+ if (extension.opts.dash) {
4970
+ pathStyle.uniformTypes = {
4971
+ dashAlignMode: "f32",
4972
+ dashGapPickable: "i32",
4973
+ dashUnits: "i32"
4974
+ };
4975
+ }
4282
4976
  return {
4283
4977
  modules: [pathStyle],
4284
4978
  defines
4285
4979
  };
4286
4980
  }
4287
4981
  initializeState(context, extension) {
4288
- const attributeManager = this.getAttributeManager();
4289
- const layerType = extension.getLayerType(this);
4290
- if (!attributeManager || !layerType) {
4291
- return;
4292
- }
4293
- if (extension.opts.dash) {
4294
- attributeManager.addInstanced({
4295
- instanceDashArrays: { size: 2, accessor: "getDashArray" },
4296
- ...layerType === "path" && extension.opts.highPrecisionDash ? {
4297
- instanceDashOffsets: {
4298
- size: 1,
4299
- accessor: "getPath",
4300
- transform: extension.getDashOffsets.bind(this)
4301
- }
4302
- } : {}
4303
- });
4304
- }
4305
- if (layerType === "path" && extension.opts.offset) {
4306
- attributeManager.addInstanced({
4307
- instanceOffsets: { size: 1, accessor: "getOffset" }
4308
- });
4309
- }
4982
+ extension.synchronizeAttributes(this);
4310
4983
  }
4311
4984
  updateState(params, extension) {
4312
4985
  if (!extension.isEnabled(this)) {
4313
4986
  return;
4314
4987
  }
4988
+ if (params.changeFlags.extensionsChanged) {
4989
+ const topologyChanged = extension.synchronizeAttributes(this);
4990
+ const attributeManager = this.getAttributeManager();
4991
+ if (attributeManager && topologyChanged) {
4992
+ attributeManager.invalidateAll();
4993
+ for (const model of this.getModels()) {
4994
+ model.setBufferLayout(attributeManager.getBufferLayouts(model));
4995
+ }
4996
+ }
4997
+ }
4315
4998
  if (extension.opts.dash) {
4316
4999
  const layerType = extension.getLayerType(this);
5000
+ if (layerType === "path" && extension.opts.dashMode === "path" && dashMetricsProjectionPropsChanged(params)) {
5001
+ this.getAttributeManager()?.invalidate("instanceDashOffsets");
5002
+ }
4317
5003
  if (layerType === "scatterplot" || layerType === "textBackground") {
4318
5004
  const pathStyleProps = {
4319
5005
  dashGapPickable: Boolean(this.props.dashGapPickable)
@@ -4322,12 +5008,101 @@ in float instanceOffsets;
4322
5008
  } else {
4323
5009
  const pathStyleProps = {
4324
5010
  dashAlignMode: this.props.dashJustified ? 1 : 0,
4325
- dashGapPickable: Boolean(this.props.dashGapPickable)
5011
+ dashGapPickable: Boolean(this.props.dashGapPickable),
5012
+ dashUnits: DASH_UNITS[this.props.dashUnits ?? "widths"]
4326
5013
  };
4327
5014
  this.setShaderModuleProps({ pathStyle: pathStyleProps });
4328
5015
  }
4329
5016
  }
4330
5017
  }
5018
+ calculateDashMetrics(attribute, { startRow, endRow }) {
5019
+ const pathTesselator = this.state.pathTesselator;
5020
+ const vertexStarts = pathTesselator?.vertexStarts;
5021
+ const instanceCount = pathTesselator?.instanceCount;
5022
+ const packedPositions = pathTesselator?.get("positions");
5023
+ if (!vertexStarts || instanceCount === void 0 || typeof pathTesselator?.getPathSegmentIndices !== "function") {
5024
+ throw new Error("PathStyleExtension requires PathLayer tessellation data for path dashes.");
5025
+ }
5026
+ const binaryPath = this.props.data?.attributes?.getPath;
5027
+ const binaryValue = ArrayBuffer.isView(binaryPath) ? binaryPath : binaryPath?.value;
5028
+ const hasReadableBinaryPath = ArrayBuffer.isView(binaryValue);
5029
+ const usePackedPositions = packedPositions && (!binaryPath || pathTesselator.normalize || pathTesselator.opts?.isWebGPU);
5030
+ if (binaryPath && !hasReadableBinaryPath || !usePackedPositions && !hasReadableBinaryPath) {
5031
+ throw new Error(
5032
+ "PathStyleExtension cannot calculate whole-path dash metrics from GPU-only getPath data; supply data.attributes.instanceDashOffsets."
5033
+ );
5034
+ }
5035
+ const output = attribute.value;
5036
+ const metricSize = attribute.size;
5037
+ const firstRow = Math.max(0, startRow);
5038
+ const lastRow = Math.min(endRow, vertexStarts.length - 1);
5039
+ let binarySize = 3;
5040
+ let binaryStride = 3;
5041
+ let binaryOffset = 0;
5042
+ if (!usePackedPositions) {
5043
+ const bytesPerElement = binaryValue.BYTES_PER_ELEMENT;
5044
+ binarySize = binaryPath?.size || (this.props.positionFormat === "XY" ? 2 : 3);
5045
+ binaryStride = binaryPath?.stride ? binaryPath.stride / bytesPerElement : binarySize;
5046
+ binaryOffset = binaryPath?.offset ? binaryPath.offset / bytesPerElement : 0;
5047
+ }
5048
+ const sourcePosition = new Array(3);
5049
+ const projectUniforms = import_core7.project.getUniforms({
5050
+ viewport: this.context.viewport,
5051
+ modelMatrix: this.props.modelMatrix,
5052
+ coordinateSystem: this.props.coordinateSystem,
5053
+ coordinateOrigin: this.props.coordinateOrigin,
5054
+ // PathLayer normalizes/cuts wrapped paths on the CPU; its shader never wraps again.
5055
+ autoWrapLongitude: this.wrapLongitude
5056
+ });
5057
+ const readPosition = (vertexIndex) => {
5058
+ if (usePackedPositions) {
5059
+ sourcePosition[0] = packedPositions[vertexIndex * 3];
5060
+ sourcePosition[1] = packedPositions[vertexIndex * 3 + 1];
5061
+ sourcePosition[2] = packedPositions[vertexIndex * 3 + 2];
5062
+ } else {
5063
+ const sourceIndex = binaryOffset + vertexIndex * binaryStride;
5064
+ sourcePosition[0] = binaryValue[sourceIndex];
5065
+ sourcePosition[1] = binaryValue[sourceIndex + 1];
5066
+ sourcePosition[2] = binarySize === 3 ? binaryValue[sourceIndex + 2] : 0;
5067
+ }
5068
+ return sourcePosition;
5069
+ };
5070
+ attribute.startIndices = vertexStarts;
5071
+ for (let row = firstRow; row < lastRow; row++) {
5072
+ const rowStart = vertexStarts[row];
5073
+ const rowEnd = Math.min(vertexStarts[row + 1] ?? instanceCount, instanceCount);
5074
+ output.fill(0, rowStart * metricSize, rowEnd * metricSize);
5075
+ const validSegments = pathTesselator.getPathSegmentIndices(row);
5076
+ let phase = 0;
5077
+ let previousSegment = -2;
5078
+ let previousEnd = null;
5079
+ for (const segmentIndex of validSegments) {
5080
+ const outputIndex = segmentIndex * metricSize;
5081
+ output[outputIndex] = phase;
5082
+ const start = segmentIndex === previousSegment + 1 && previousEnd ? previousEnd : projectRenderedPathPosition(this, readPosition(segmentIndex), projectUniforms);
5083
+ const end = projectRenderedPathPosition(
5084
+ this,
5085
+ readPosition(segmentIndex + 1),
5086
+ projectUniforms
5087
+ );
5088
+ phase += vec3_exports.dist(start, end);
5089
+ previousSegment = segmentIndex;
5090
+ previousEnd = end;
5091
+ }
5092
+ if (metricSize > 1) {
5093
+ for (let vertexIndex = rowStart; vertexIndex < rowEnd; vertexIndex++) {
5094
+ output[vertexIndex * metricSize + 1] = phase;
5095
+ }
5096
+ }
5097
+ }
5098
+ }
5099
+ /**
5100
+ * Calculates the distance from the start of a path to each rendered segment.
5101
+ *
5102
+ * The final entry is zero because PathLayer reserves the final vertex as invalid padding.
5103
+ * This scalar return shape is retained for compatibility; path-mode rendering uses an
5104
+ * internal attribute containing both each segment offset and the total path length.
5105
+ */
4331
5106
  getDashOffsets(path) {
4332
5107
  const result = [0];
4333
5108
  const positionSize = this.props.positionFormat === "XY" ? 2 : 3;
@@ -4337,7 +5112,7 @@ in float instanceOffsets;
4337
5112
  let prevP;
4338
5113
  for (let i = 0; i < geometrySize - 1; i++) {
4339
5114
  p = isNested ? path[i] : path.slice(i * positionSize, i * positionSize + positionSize);
4340
- p = this.projectPosition(p);
5115
+ p = this.projectPosition(p, { autoOffset: false });
4341
5116
  if (i > 0) {
4342
5117
  result[i] = result[i - 1] + vec3_exports.dist(prevP, p);
4343
5118
  }
@@ -4355,12 +5130,26 @@ in float instanceOffsets;
4355
5130
 
4356
5131
  // src/fill-style/shader-module.ts
4357
5132
  var import_core9 = __toESM(require_core(), 1);
5133
+ var FILL_UV_SCALE = 512 / 4e7;
5134
+ function getPatternUnitScale(sizeUnits, viewport) {
5135
+ switch (sizeUnits) {
5136
+ case "pixels":
5137
+ return 2 ** -Math.round(viewport.zoom);
5138
+ case "common":
5139
+ return 1;
5140
+ default:
5141
+ return FILL_UV_SCALE;
5142
+ }
5143
+ }
4358
5144
  var uniformBlock3 = (
4359
5145
  /* glsl */
4360
5146
  `layout(std140) uniform fillUniforms {
4361
5147
  vec2 patternTextureSize;
5148
+ float patternUnitScale;
4362
5149
  bool patternEnabled;
4363
5150
  bool patternMask;
5151
+ bool procedural;
5152
+ bool flipY;
4364
5153
  vec2 uvCoordinateOrigin;
4365
5154
  vec2 uvCoordinateOrigin64Low;
4366
5155
  } fill;
@@ -4369,13 +5158,20 @@ in float instanceOffsets;
4369
5158
  var patternVs = (
4370
5159
  /* glsl */
4371
5160
  `
5161
+ uniform sampler2D fill_patternTexture;
5162
+
4372
5163
  in vec4 fillPatternFrames;
4373
5164
  in float fillPatternScales;
4374
5165
  in vec2 fillPatternOffsets;
5166
+ in vec4 fillPatternBackgroundColors;
4375
5167
 
4376
5168
  out vec2 fill_uv;
4377
5169
  out vec4 fill_patternBounds;
4378
5170
  out vec4 fill_patternPlacement;
5171
+ out vec4 fill_backgroundColor;
5172
+ out float fill_patternCoordinateScale;
5173
+ flat out vec4 fill_patternParams0;
5174
+ flat out vec4 fill_patternParams1;
4379
5175
  `
4380
5176
  );
4381
5177
  var vs3 = `
@@ -4389,9 +5185,136 @@ uniform sampler2D fill_patternTexture;
4389
5185
 
4390
5186
  in vec4 fill_patternBounds;
4391
5187
  in vec4 fill_patternPlacement;
5188
+ in vec4 fill_backgroundColor;
4392
5189
  in vec2 fill_uv;
5190
+ in float fill_patternCoordinateScale;
5191
+ flat in vec4 fill_patternParams0;
5192
+ flat in vec4 fill_patternParams1;
5193
+
5194
+ const float FILL_PATTERN_HATCH = 1.0;
5195
+ const float FILL_PATTERN_CROSS_HATCH = 2.0;
5196
+ const float FILL_PATTERN_DOTS = 3.0;
5197
+
5198
+ float fill_getPatternCoordinate(vec2 axis, float period, float offset) {
5199
+ float coordinateScale = max(abs(fill_patternCoordinateScale), 1.0e-20);
5200
+ vec2 coordinateOrigin = fill.uvCoordinateOrigin;
5201
+ vec2 coordinateOffset = fill.uvCoordinateOrigin64Low + fill_uv;
5202
+ float yDirection = fill.flipY ? 1.0 : -1.0;
5203
+ coordinateOrigin.y *= yDirection;
5204
+ coordinateOffset.y *= yDirection;
5205
+
5206
+ // Reduce the high part before adding the local offset to retain precision in large coordinates.
5207
+ float highPart = mod(dot(coordinateOrigin, axis) / coordinateScale, period);
5208
+ float lowPart = dot(coordinateOffset, axis) / coordinateScale;
5209
+ return mod(highPart + lowPart + offset * period, period);
5210
+ }
5211
+
5212
+ vec2 fill_getPatternCoords(
5213
+ vec2 axis0,
5214
+ vec2 axis1,
5215
+ vec2 period,
5216
+ vec2 offset
5217
+ ) {
5218
+ return vec2(
5219
+ fill_getPatternCoordinate(axis0, period.x, offset.x),
5220
+ fill_getPatternCoordinate(axis1, period.y, offset.y)
5221
+ );
5222
+ }
5223
+
5224
+ float fill_getScreenSpaceAlpha(float signedDistance) {
5225
+ float distanceDerivative = fwidth(signedDistance);
5226
+ if (distanceDerivative == 0.0) {
5227
+ return signedDistance <= 0.0 ? 1.0 : 0.0;
5228
+ }
5229
+ // Normalize the SDF to screen pixels so the coverage ramp is independent of pattern units.
5230
+ float distanceInPixels = signedDistance / distanceDerivative;
5231
+ return 1.0 - smoothstep(-0.5, 0.5, distanceInPixels);
5232
+ }
5233
+
5234
+ float fill_getLineAlpha(float position, float center, float period, float strokeWidth) {
5235
+ float distanceToLine = abs(mod(position - center + period * 0.5, period) - period * 0.5);
5236
+ return fill_getScreenSpaceAlpha(distanceToLine - strokeWidth * 0.5);
5237
+ }
4393
5238
 
4394
- const float FILL_UV_SCALE = 512.0 / 40000000.0;
5239
+ float fill_getHatchAlpha(float angle, float strokeWidth, vec2 gaps, bool alternating) {
5240
+ vec2 lineDirection = vec2(cos(angle), sin(angle));
5241
+ vec2 lineNormal = vec2(-lineDirection.y, lineDirection.x);
5242
+ float period = alternating
5243
+ ? strokeWidth * 2.0 + gaps.x + gaps.y
5244
+ : strokeWidth + gaps.x;
5245
+ float offset = dot(fill_patternPlacement.xy, lineNormal);
5246
+ float position = fill_getPatternCoordinate(lineNormal, period, offset);
5247
+ float alpha = fill_getLineAlpha(position, 0.0, period, strokeWidth);
5248
+ if (alternating) {
5249
+ alpha = max(
5250
+ alpha,
5251
+ fill_getLineAlpha(position, strokeWidth + gaps.x, period, strokeWidth)
5252
+ );
5253
+ }
5254
+ return alpha;
5255
+ }
5256
+
5257
+ float fill_getDotAlpha(float angle, float skew, float radius, float gap) {
5258
+ vec2 axis0 = vec2(cos(angle), sin(angle));
5259
+ float axis1Angle = angle + 1.5707963267948966 - skew;
5260
+ vec2 axis1 = vec2(cos(axis1Angle), sin(axis1Angle));
5261
+ float determinant = axis0.x * axis1.y - axis0.y * axis1.x;
5262
+
5263
+ // The reciprocal basis maps common coordinates into the skewed dot lattice.
5264
+ vec2 reciprocal0 = vec2(axis1.y, -axis1.x) / determinant;
5265
+ vec2 reciprocal1 = vec2(-axis0.y, axis0.x) / determinant;
5266
+ float period = radius * 2.0 + gap;
5267
+ vec2 latticeCoords = fill_getPatternCoords(
5268
+ reciprocal0,
5269
+ reciprocal1,
5270
+ vec2(period),
5271
+ fill_patternPlacement.xy
5272
+ );
5273
+ vec2 cellOffset = mod(latticeCoords + period * 0.5, period) - period * 0.5;
5274
+ float distanceToDotSquared = 1.0e20;
5275
+ // In a skewed lattice the closest dot is not necessarily in the independently wrapped cell.
5276
+ // Include adjacent cells to keep the repeated distance field continuous at cell boundaries.
5277
+ for (int x = -1; x <= 1; x++) {
5278
+ for (int y = -1; y <= 1; y++) {
5279
+ vec2 neighborOffset = cellOffset + vec2(float(x), float(y)) * period;
5280
+ vec2 dotOffset = axis0 * neighborOffset.x + axis1 * neighborOffset.y;
5281
+ distanceToDotSquared = min(distanceToDotSquared, dot(dotOffset, dotOffset));
5282
+ }
5283
+ }
5284
+ float distanceToDot = sqrt(distanceToDotSquared);
5285
+ return fill_getScreenSpaceAlpha(distanceToDot - radius);
5286
+ }
5287
+
5288
+ float fill_getProceduralPatternAlpha() {
5289
+ float patternType = fill_patternParams0.x;
5290
+ float size = fill_patternParams0.y;
5291
+ vec2 gaps = fill_patternParams0.zw;
5292
+ vec2 angles = fill_patternParams1.xy;
5293
+ float elementCount = fill_patternParams1.z;
5294
+
5295
+ if (patternType == FILL_PATTERN_HATCH) {
5296
+ return fill_getHatchAlpha(angles.x, size, gaps, elementCount == 2.0);
5297
+ }
5298
+ if (patternType == FILL_PATTERN_CROSS_HATCH) {
5299
+ return max(
5300
+ fill_getHatchAlpha(angles.x, size, gaps, false),
5301
+ fill_getHatchAlpha(angles.y, size, gaps, false)
5302
+ );
5303
+ }
5304
+ if (patternType == FILL_PATTERN_DOTS) {
5305
+ return fill_getDotAlpha(angles.x, angles.y, size, gaps.x);
5306
+ }
5307
+ return 1.0;
5308
+ }
5309
+
5310
+ // Draw the pattern over the background fill (Porter-Duff source-over)
5311
+ vec4 fill_blendOverBackground(vec4 pattern, vec4 background) {
5312
+ if (background.a == 0.0 || layer.opacity == 0.0) return pattern;
5313
+ float patternAlpha = pattern.a / layer.opacity;
5314
+ float blendedAlpha = patternAlpha + background.a * (1.0 - patternAlpha);
5315
+ vec3 blendedRGB = mix(background.rgb, pattern.rgb, patternAlpha / blendedAlpha);
5316
+ return vec4(blendedRGB, blendedAlpha * layer.opacity);
5317
+ }
4395
5318
  `
4396
5319
  );
4397
5320
  var fs3 = `
@@ -4402,16 +5325,32 @@ ${patternFs}
4402
5325
  "vs:DECKGL_FILTER_GL_POSITION": (
4403
5326
  /* glsl */
4404
5327
  `
4405
- fill_uv = geometry.position.xy;
4406
- `
4407
- ),
4408
- "vs:DECKGL_FILTER_COLOR": (
4409
- /* glsl */
4410
- `
4411
5328
  if (fill.patternEnabled) {
4412
- fill_patternBounds = fillPatternFrames / vec4(fill.patternTextureSize, fill.patternTextureSize);
4413
5329
  fill_patternPlacement.xy = fillPatternOffsets;
4414
- fill_patternPlacement.zw = fillPatternScales * fillPatternFrames.zw;
5330
+ fill_backgroundColor = fillPatternBackgroundColors;
5331
+ if (fill.procedural) {
5332
+ fill_uv = geometry.position.xy;
5333
+ fill_patternCoordinateScale = fill.patternUnitScale * fillPatternScales;
5334
+ int texelIndex = int(fillPatternFrames.x) * 4;
5335
+ fill_patternParams0 = vec4(
5336
+ texelFetch(fill_patternTexture, ivec2(texelIndex, 0), 0).rg,
5337
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 1, 0), 0).rg
5338
+ );
5339
+ fill_patternParams1 = vec4(
5340
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 2, 0), 0).rg,
5341
+ texelFetch(fill_patternTexture, ivec2(texelIndex + 3, 0), 0).rg
5342
+ );
5343
+ } else {
5344
+ fill_patternBounds = fillPatternFrames / vec4(fill.patternTextureSize, fill.patternTextureSize);
5345
+ vec2 patternFrameCommon = fill.patternUnitScale * fillPatternScales * fillPatternFrames.zw;
5346
+ // Reduce the coordinate origin to within one tile before adding the vertex position. The
5347
+ // origin is large in common space, and fp32 cannot carry the sum at full precision.
5348
+ vec2 origin = mod(fill.uvCoordinateOrigin, patternFrameCommon) + fill.uvCoordinateOrigin64Low;
5349
+ fill_uv = (origin + geometry.position.xy) / patternFrameCommon;
5350
+ // Pattern atlases use top-left coordinates, so reverse common-space Y in bottom-left views.
5351
+ fill_uv.y *= fill.flipY ? 1.0 : -1.0;
5352
+ fill_uv += fillPatternOffsets;
5353
+ }
4415
5354
  }
4416
5355
  `
4417
5356
  ),
@@ -4419,17 +5358,25 @@ ${patternFs}
4419
5358
  /* glsl */
4420
5359
  `
4421
5360
  if (fill.patternEnabled) {
4422
- vec2 scale = FILL_UV_SCALE * fill_patternPlacement.zw;
4423
- vec2 patternUV = mod(mod(fill.uvCoordinateOrigin, scale) + fill.uvCoordinateOrigin64Low + fill_uv, scale) / scale;
4424
- patternUV = mod(fill_patternPlacement.xy + patternUV, 1.0);
4425
-
4426
- vec2 texCoords = fill_patternBounds.xy + fill_patternBounds.zw * patternUV;
4427
-
4428
- vec4 patternColor = texture(fill_patternTexture, texCoords);
4429
- color.a *= patternColor.a;
4430
- if (!fill.patternMask) {
4431
- color.rgb = patternColor.rgb;
5361
+ if (fill.procedural) {
5362
+ color.a *= fill_getProceduralPatternAlpha();
5363
+ } else {
5364
+ vec2 patternUV = fract(fill_uv);
5365
+ vec2 texCoords = fill_patternBounds.xy + fill_patternBounds.zw * patternUV;
5366
+
5367
+ // Tiling is emulated by wrapping the coordinate, so texCoords jumps from the end of the
5368
+ // frame back to its start once per tile, leading to the wrong mip level being selected,
5369
+ // which leads to artifacts at pattern edges. fill_uv is continuous across the primitive,
5370
+ // meaning that the correct mip level is always selected
5371
+ vec4 grad = fill_patternBounds.zwzw * vec4(dFdx(fill_uv), dFdy(fill_uv));
5372
+ vec4 patternColor = textureGrad(fill_patternTexture, texCoords, grad.xy, grad.zw);
5373
+
5374
+ color.a *= patternColor.a;
5375
+ if (!fill.patternMask) {
5376
+ color.rgb = patternColor.rgb;
5377
+ }
4432
5378
  }
5379
+ color = fill_blendOverBackground(color, fill_backgroundColor);
4433
5380
  }
4434
5381
  `
4435
5382
  )
@@ -4445,17 +5392,28 @@ ${patternFs}
4445
5392
  uniforms.patternTextureSize = [fillPatternTexture.width, fillPatternTexture.height];
4446
5393
  }
4447
5394
  if ("project" in opts) {
4448
- const { fillPatternMask = true, fillPatternEnabled = true } = opts;
5395
+ const {
5396
+ fillPatternMask = true,
5397
+ fillPatternEnabled = true,
5398
+ fillPatternSizeUnits = "meters",
5399
+ fillPatternCommonFrame = null,
5400
+ procedural = false
5401
+ } = opts;
4449
5402
  const projectUniforms = import_core9.project.getUniforms(opts.project);
4450
5403
  const { commonOrigin: coordinateOriginCommon } = projectUniforms;
4451
- const coordinateOriginCommon64Low = [
4452
- (0, import_core9.fp64LowPart)(coordinateOriginCommon[0]),
4453
- (0, import_core9.fp64LowPart)(coordinateOriginCommon[1])
4454
- ];
4455
- uniforms.uvCoordinateOrigin = coordinateOriginCommon.slice(0, 2);
4456
- uniforms.uvCoordinateOrigin64Low = coordinateOriginCommon64Low;
5404
+ const unitScale = getPatternUnitScale(fillPatternSizeUnits, opts.project.viewport);
5405
+ const origin = [coordinateOriginCommon[0], coordinateOriginCommon[1]];
5406
+ if (fillPatternCommonFrame) {
5407
+ origin[0] %= unitScale * fillPatternCommonFrame[0];
5408
+ origin[1] %= unitScale * fillPatternCommonFrame[1];
5409
+ }
5410
+ uniforms.uvCoordinateOrigin = origin;
5411
+ uniforms.uvCoordinateOrigin64Low = [(0, import_core9.fp64LowPart)(origin[0]), (0, import_core9.fp64LowPart)(origin[1])];
5412
+ uniforms.patternUnitScale = unitScale;
4457
5413
  uniforms.patternMask = fillPatternMask;
4458
5414
  uniforms.patternEnabled = fillPatternEnabled;
5415
+ uniforms.procedural = procedural;
5416
+ uniforms.flipY = opts.project.viewport?.flipY ?? false;
4459
5417
  }
4460
5418
  return uniforms;
4461
5419
  }
@@ -4468,31 +5426,172 @@ ${patternFs}
4468
5426
  getUniforms: getPatternUniforms,
4469
5427
  uniformTypes: {
4470
5428
  patternTextureSize: "vec2<f32>",
5429
+ patternUnitScale: "f32",
4471
5430
  patternEnabled: "i32",
4472
5431
  patternMask: "i32",
5432
+ procedural: "i32",
5433
+ flipY: "i32",
4473
5434
  uvCoordinateOrigin: "vec2<f32>",
4474
5435
  uvCoordinateOrigin64Low: "vec2<f32>"
4475
5436
  }
4476
5437
  };
4477
5438
 
5439
+ // src/fill-style/procedural-pattern.ts
5440
+ var PROCEDURAL_PATTERN_TEXTURE_FORMAT = "rg32float";
5441
+ var PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL = 2;
5442
+ var PROCEDURAL_PATTERN_TEXELS_PER_PATTERN = 4;
5443
+ var PROCEDURAL_PATTERN_TYPES = {
5444
+ none: 0,
5445
+ hatch: 1,
5446
+ crossHatch: 2,
5447
+ dots: 3
5448
+ };
5449
+ var PROCEDURAL_PATTERN_OFFSETS = {
5450
+ type: 0,
5451
+ size: 1,
5452
+ gap0: 2,
5453
+ gap1: 3,
5454
+ angle0: 4,
5455
+ angle1: 5,
5456
+ /** Number of alternating gaps, hatch directions, or dot-grid axes, depending on type. */
5457
+ elementCount: 6
5458
+ };
5459
+ var COMPONENTS_PER_PATTERN = PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL * PROCEDURAL_PATTERN_TEXELS_PER_PATTERN;
5460
+ var DEGREES_TO_RADIANS2 = Math.PI / 180;
5461
+ var DEFAULT_SIZE = 1;
5462
+ var DEFAULT_GAP = 1;
5463
+ var DEFAULT_HATCH_ANGLE = 0;
5464
+ var DEFAULT_CROSS_HATCH_ANGLES = [45, 135];
5465
+ var DEFAULT_DOT_ANGLE = 0;
5466
+ var DEFAULT_DOT_SKEW = 0;
5467
+ function packProceduralPatterns(mapping) {
5468
+ const entries = Object.entries(mapping);
5469
+ const data = new Float32Array((entries.length + 1) * COMPONENTS_PER_PATTERN);
5470
+ const patternIndices = /* @__PURE__ */ new Map();
5471
+ entries.forEach(([id, config2], entryIndex) => {
5472
+ const patternIndex = entryIndex + 1;
5473
+ const offset = patternIndex * COMPONENTS_PER_PATTERN;
5474
+ patternIndices.set(id, patternIndex);
5475
+ switch (config2.type) {
5476
+ case "hatch": {
5477
+ const gap = config2.gap === void 0 ? DEFAULT_GAP : config2.gap;
5478
+ const strokeWidth = config2.strokeWidth === void 0 ? DEFAULT_SIZE : config2.strokeWidth;
5479
+ const angle2 = config2.angle === void 0 ? DEFAULT_HATCH_ANGLE : config2.angle;
5480
+ if (Array.isArray(gap) && gap.length !== 2) {
5481
+ throw new Error(`${id}.gap must be a number or a two-element array`);
5482
+ }
5483
+ const gaps = Array.isArray(gap) ? gap : [gap, gap];
5484
+ validatePositive(strokeWidth, `${id}.strokeWidth`);
5485
+ validateNonNegative(gaps[0], `${id}.gap[0]`);
5486
+ validateNonNegative(gaps[1], `${id}.gap[1]`);
5487
+ validateAngle(angle2, `${id}.angle`);
5488
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.hatch;
5489
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = strokeWidth;
5490
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gaps[0];
5491
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gaps[1];
5492
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angle2 * DEGREES_TO_RADIANS2;
5493
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = Array.isArray(gap) ? 2 : 1;
5494
+ break;
5495
+ }
5496
+ case "cross-hatch": {
5497
+ const angles = config2.angles === void 0 ? DEFAULT_CROSS_HATCH_ANGLES : config2.angles;
5498
+ const strokeWidth = config2.strokeWidth === void 0 ? DEFAULT_SIZE : config2.strokeWidth;
5499
+ const gap = config2.gap === void 0 ? DEFAULT_GAP : config2.gap;
5500
+ if (!Array.isArray(angles) || angles.length !== 2) {
5501
+ throw new Error(`${id}.angles must be a two-element array`);
5502
+ }
5503
+ validatePositive(strokeWidth, `${id}.strokeWidth`);
5504
+ validateNonNegative(gap, `${id}.gap`);
5505
+ validateAngle(angles[0], `${id}.angles[0]`);
5506
+ validateAngle(angles[1], `${id}.angles[1]`);
5507
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.crossHatch;
5508
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = strokeWidth;
5509
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gap;
5510
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gap;
5511
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angles[0] * DEGREES_TO_RADIANS2;
5512
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle1] = angles[1] * DEGREES_TO_RADIANS2;
5513
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = 2;
5514
+ break;
5515
+ }
5516
+ case "dots": {
5517
+ const radius = config2.radius === void 0 ? DEFAULT_SIZE : config2.radius;
5518
+ const gap = config2.gap === void 0 ? DEFAULT_GAP : config2.gap;
5519
+ const angle2 = config2.angle === void 0 ? DEFAULT_DOT_ANGLE : config2.angle;
5520
+ const skew = config2.skew === void 0 ? DEFAULT_DOT_SKEW : config2.skew;
5521
+ validatePositive(radius, `${id}.radius`);
5522
+ validateNonNegative(gap, `${id}.gap`);
5523
+ validateAngle(angle2, `${id}.angle`);
5524
+ validateSkew(skew, `${id}.skew`);
5525
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.type] = PROCEDURAL_PATTERN_TYPES.dots;
5526
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.size] = radius;
5527
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap0] = gap;
5528
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.gap1] = gap;
5529
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle0] = angle2 * DEGREES_TO_RADIANS2;
5530
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.angle1] = skew * DEGREES_TO_RADIANS2;
5531
+ data[offset + PROCEDURAL_PATTERN_OFFSETS.elementCount] = 2;
5532
+ break;
5533
+ }
5534
+ default:
5535
+ throw new Error(`${id}.type is not a supported procedural pattern type`);
5536
+ }
5537
+ });
5538
+ return {
5539
+ data,
5540
+ width: data.length / PROCEDURAL_PATTERN_COMPONENTS_PER_TEXEL,
5541
+ height: 1,
5542
+ patternIndices
5543
+ };
5544
+ }
5545
+ function validatePositive(value, name) {
5546
+ if (!Number.isFinite(value) || value <= 0) {
5547
+ throw new Error(`${name} must be a finite number greater than 0`);
5548
+ }
5549
+ }
5550
+ function validateNonNegative(value, name) {
5551
+ if (!Number.isFinite(value) || value < 0) {
5552
+ throw new Error(`${name} must be a finite number greater than or equal to 0`);
5553
+ }
5554
+ }
5555
+ function validateAngle(value, name) {
5556
+ if (!Number.isFinite(value)) {
5557
+ throw new Error(`${name} must be a finite number`);
5558
+ }
5559
+ }
5560
+ function validateSkew(value, name) {
5561
+ if (!Number.isFinite(value) || value <= -90 || value >= 90) {
5562
+ throw new Error(`${name} must be a finite number greater than -90 and less than 90`);
5563
+ }
5564
+ }
5565
+
4478
5566
  // src/fill-style/fill-style-extension.ts
5567
+ function leastCommonMultiple(a, b) {
5568
+ if (!Number.isInteger(b) || b <= 0) return 0;
5569
+ if (a === 0) return b;
5570
+ let [x, y] = [a, b];
5571
+ while (y > 0) [x, y] = [y, x % y];
5572
+ return a / x * b;
5573
+ }
4479
5574
  var defaultProps4 = {
4480
5575
  fillPatternEnabled: true,
4481
5576
  fillPatternAtlas: {
4482
5577
  type: "image",
4483
5578
  value: null,
4484
- async: true,
4485
- parameters: { lodMaxClamp: 0 }
5579
+ async: true
4486
5580
  },
4487
5581
  fillPatternMapping: { type: "object", value: {}, async: true },
4488
5582
  fillPatternMask: true,
5583
+ fillPatternSizeUnits: "meters",
4489
5584
  getFillPattern: { type: "accessor", value: (d) => d.pattern },
4490
5585
  getFillPatternScale: { type: "accessor", value: 1 },
4491
- getFillPatternOffset: { type: "accessor", value: [0, 0] }
5586
+ getFillPatternOffset: { type: "accessor", value: [0, 0] },
5587
+ getFillPatternBackgroundColor: { type: "accessor", value: [0, 0, 0, 0] }
4492
5588
  };
4493
5589
  var FillStyleExtension = class extends import_core10.LayerExtension {
4494
- constructor({ pattern = false } = {}) {
4495
- super({ pattern });
5590
+ constructor({
5591
+ pattern = false,
5592
+ proceduralPattern = false
5593
+ } = {}) {
5594
+ super({ pattern: pattern || proceduralPattern, proceduralPattern });
4496
5595
  }
4497
5596
  isEnabled(layer) {
4498
5597
  return layer.getAttributeManager() !== null && !("pathTesselator" in layer.state);
@@ -4516,7 +5615,7 @@ ${patternFs}
4516
5615
  size: 4,
4517
5616
  stepMode: "dynamic",
4518
5617
  accessor: "getFillPattern",
4519
- transform: extension.getPatternFrame.bind(this)
5618
+ transform: extension.opts.proceduralPattern ? extension.getProceduralPatternIndex.bind(this) : extension.getPatternFrame.bind(this)
4520
5619
  },
4521
5620
  fillPatternScales: {
4522
5621
  size: 1,
@@ -4528,6 +5627,13 @@ ${patternFs}
4528
5627
  size: 2,
4529
5628
  stepMode: "dynamic",
4530
5629
  accessor: "getFillPatternOffset"
5630
+ },
5631
+ fillPatternBackgroundColors: {
5632
+ size: 4,
5633
+ type: "unorm8",
5634
+ stepMode: "dynamic",
5635
+ accessor: "getFillPatternBackgroundColor",
5636
+ defaultValue: [0, 0, 0, 255]
4531
5637
  }
4532
5638
  });
4533
5639
  }
@@ -4536,7 +5642,8 @@ ${patternFs}
4536
5642
  data: new Uint8Array(4),
4537
5643
  width: 1,
4538
5644
  height: 1
4539
- })
5645
+ }),
5646
+ ...extension.opts.proceduralPattern && extension.createProceduralPatternTexture.call(this, {})
4540
5647
  });
4541
5648
  }
4542
5649
  updateState({ props, oldProps }, extension) {
@@ -4544,30 +5651,84 @@ ${patternFs}
4544
5651
  return;
4545
5652
  }
4546
5653
  if (props.fillPatternMapping && props.fillPatternMapping !== oldProps.fillPatternMapping) {
5654
+ if (extension.opts.proceduralPattern && typeof props.fillPatternMapping !== "string") {
5655
+ const oldTexture = this.state.proceduralPatternTexture;
5656
+ this.setState(
5657
+ extension.createProceduralPatternTexture.call(
5658
+ this,
5659
+ props.fillPatternMapping
5660
+ )
5661
+ );
5662
+ oldTexture?.delete();
5663
+ }
4547
5664
  this.getAttributeManager().invalidate("getFillPattern");
4548
5665
  }
5666
+ if (props.fillPatternMapping !== oldProps.fillPatternMapping || props.getFillPatternScale !== oldProps.getFillPatternScale) {
5667
+ this.setState({ commonFrame: extension.getCommonFrame(props) });
5668
+ }
4549
5669
  }
4550
5670
  draw(params, extension) {
4551
5671
  if (!extension.isEnabled(this)) {
4552
5672
  return;
4553
5673
  }
4554
- const { fillPatternAtlas, fillPatternEnabled, fillPatternMask } = this.props;
5674
+ const { fillPatternAtlas, fillPatternEnabled, fillPatternMask, fillPatternSizeUnits } = this.props;
4555
5675
  const fillProps = {
4556
5676
  project: params.shaderModuleProps.project,
4557
5677
  fillPatternEnabled,
4558
5678
  fillPatternMask,
4559
- fillPatternTexture: fillPatternAtlas || this.state.emptyTexture
5679
+ fillPatternSizeUnits,
5680
+ procedural: extension.opts.proceduralPattern,
5681
+ fillPatternTexture: extension.opts.proceduralPattern ? this.state.proceduralPatternTexture || this.state.emptyTexture : fillPatternAtlas || this.state.emptyTexture,
5682
+ fillPatternCommonFrame: this.state.commonFrame
4560
5683
  };
4561
5684
  this.setShaderModuleProps({ fill: fillProps });
4562
5685
  }
4563
5686
  finalizeState() {
4564
5687
  const emptyTexture = this.state.emptyTexture;
4565
5688
  emptyTexture?.delete();
5689
+ const proceduralPatternTexture = this.state.proceduralPatternTexture;
5690
+ proceduralPatternTexture?.delete();
4566
5691
  }
4567
5692
  getPatternFrame(name) {
4568
5693
  const { fillPatternMapping } = this.getCurrentLayer().props;
4569
5694
  const def = fillPatternMapping && fillPatternMapping[name];
4570
- return def ? [def.x, def.y, def.width, def.height] : [0, 0, 0, 0];
5695
+ return def && "x" in def ? [def.x, def.y, def.width, def.height] : [0, 0, 0, 0];
5696
+ }
5697
+ /**
5698
+ * Returns common frame into which all patterns fit as integer multiples
5699
+ */
5700
+ getCommonFrame(props) {
5701
+ const { fillPatternMapping, getFillPatternScale: scale3 } = props;
5702
+ if (typeof scale3 !== "number" || !(scale3 > 0) || typeof fillPatternMapping !== "object") {
5703
+ return null;
5704
+ }
5705
+ let width = 0;
5706
+ let height = 0;
5707
+ for (const name in fillPatternMapping) {
5708
+ const frame = fillPatternMapping[name];
5709
+ if (!("width" in frame)) return null;
5710
+ width = leastCommonMultiple(width, frame.width);
5711
+ height = leastCommonMultiple(height, frame.height);
5712
+ if (!width || !height) return null;
5713
+ }
5714
+ return width ? [scale3 * width, scale3 * height] : null;
5715
+ }
5716
+ getProceduralPatternIndex(name) {
5717
+ const patternIndices = this.state.proceduralPatternIndices;
5718
+ return [patternIndices?.get(name) ?? 0, 0, 0, 0];
5719
+ }
5720
+ createProceduralPatternTexture(mapping) {
5721
+ const packedPatterns = packProceduralPatterns(mapping);
5722
+ return {
5723
+ proceduralPatternIndices: packedPatterns.patternIndices,
5724
+ proceduralPatternTexture: this.context.device.createTexture({
5725
+ data: packedPatterns.data,
5726
+ width: packedPatterns.width,
5727
+ height: packedPatterns.height,
5728
+ format: PROCEDURAL_PATTERN_TEXTURE_FORMAT,
5729
+ sampler: { minFilter: "nearest", magFilter: "nearest" }
5730
+ })
5731
+ };
4571
5732
  }
4572
5733
  };
4573
5734
  FillStyleExtension.defaultProps = defaultProps4;
@@ -5320,12 +6481,12 @@ in vec2 mask_texCoords;
5320
6481
  if (zoom === void 0) {
5321
6482
  width = width - border * 2;
5322
6483
  height = height - border * 2;
5323
- const scale2 = Math.min(width / (bounds[2] - bounds[0]), height / (bounds[3] - bounds[1]));
5324
- zoom = Math.min(Math.log2(scale2), 20);
6484
+ const scale3 = Math.min(width / (bounds[2] - bounds[0]), height / (bounds[3] - bounds[1]));
6485
+ zoom = Math.min(Math.log2(scale3), 20);
5325
6486
  } else if (!width || !height) {
5326
- const scale2 = 2 ** zoom;
5327
- width = Math.round(Math.abs(bounds[2] - bounds[0]) * scale2);
5328
- height = Math.round(Math.abs(bounds[3] - bounds[1]) * scale2);
6487
+ const scale3 = 2 ** zoom;
6488
+ width = Math.round(Math.abs(bounds[2] - bounds[0]) * scale3);
6489
+ height = Math.round(Math.abs(bounds[3] - bounds[1]) * scale3);
5329
6490
  const maxSize = MAX_VIEWPORT_SIZE - border * 2;
5330
6491
  if (width > maxSize || height > maxSize) {
5331
6492
  const r = maxSize / Math.max(width, height);
@@ -6915,9 +8076,9 @@ if ((terrain.mode == TERRAIN_MODE_USE_COVER) || (terrain.mode == TERRAIN_MODE_US
6915
8076
  }
6916
8077
  this.bounds = bounds;
6917
8078
  this.lastViewport = viewport;
6918
- const scale2 = viewport.scale;
6919
- const pixelWidth = (bounds[2] - bounds[0]) * scale2;
6920
- const pixelHeight = (bounds[3] - bounds[1]) * scale2;
8079
+ const scale3 = viewport.scale;
8080
+ const pixelWidth = (bounds[2] - bounds[0]) * scale3;
8081
+ const pixelHeight = (bounds[3] - bounds[1]) * scale3;
6921
8082
  const centerMerc = viewport.isGeospatial ? lngLatToMercatorCommon([
6922
8083
  viewport.longitude ?? 0,
6923
8084
  viewport.latitude ?? 0