@deck.gl/aggregation-layers 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.
package/dist/index.cjs CHANGED
@@ -759,7 +759,7 @@ fn project_mercator_(lnglat: vec2<f32>) -> vec2<f32> {
759
759
  let y = clamp(lnglat.y, -89.9, 89.9);
760
760
  return vec2<f32>(
761
761
  radians(x) + PI,
762
- PI + log(tan(PI * 0.25 + radians(y) * 0.5))
762
+ PI + log(tan_fp32(PI * 0.25 + radians(y) * 0.5))
763
763
  ) * WORLD_SCALE;
764
764
  }
765
765
 
@@ -1739,8 +1739,8 @@ var _Viewport = class _Viewport {
1739
1739
  }
1740
1740
  return PROJECTION_MODE.IDENTITY;
1741
1741
  }
1742
- // Two viewports are equal if width and height are identical, and if
1743
- // their view and projection matrices are (approximately) equal.
1742
+ // Two viewports are equal if their dimensions, common-space scale, and
1743
+ // view and projection matrices are (approximately) equal.
1744
1744
  equals(viewport) {
1745
1745
  if (!(viewport instanceof _Viewport)) {
1746
1746
  return false;
@@ -1748,7 +1748,7 @@ var _Viewport = class _Viewport {
1748
1748
  if (this === viewport) {
1749
1749
  return true;
1750
1750
  }
1751
- 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);
1751
+ 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);
1752
1752
  }
1753
1753
  /**
1754
1754
  * Projects xyz (possibly latitude and longitude) to pixel coordinates in window
@@ -2085,6 +2085,10 @@ var _WebMercatorViewport = class _WebMercatorViewport extends Viewport {
2085
2085
  }
2086
2086
  return this._subViewports;
2087
2087
  }
2088
+ /** Returns whether two Web Mercator viewports use the same projection settings. */
2089
+ equals(viewport) {
2090
+ return viewport instanceof _WebMercatorViewport && viewport._pseudoMeters === this._pseudoMeters && super.equals(viewport);
2091
+ }
2088
2092
  projectPosition(xyz) {
2089
2093
  if (this._pseudoMeters) {
2090
2094
  return super.projectPosition(xyz);
@@ -3242,6 +3246,7 @@ var DataColumn = class {
3242
3246
  this._buffer = null;
3243
3247
  }
3244
3248
  typed_array_manager_default.release(this.state.allocatedValue);
3249
+ this.state.allocatedValue = null;
3245
3250
  }
3246
3251
  getBuffer() {
3247
3252
  if (this.state.constant && this.device.type !== "webgpu") {
@@ -4191,6 +4196,7 @@ ${getComputeBlock(module2.name, inputEntries, output, elementWise, expression)}
4191
4196
  );
4192
4197
  const computation = new import_engine.Computation(outputBuffer.device, {
4193
4198
  source: source4,
4199
+ modules: module2.dependencies,
4194
4200
  shaderAssembler: GPGPU_SHADER_ASSEMBLER,
4195
4201
  shaderLayout: {
4196
4202
  bindings: [
@@ -10599,6 +10605,25 @@ var PathTesselator = class extends Tesselator {
10599
10605
  get(attributeName) {
10600
10606
  return this.attributes[attributeName];
10601
10607
  }
10608
+ /** Get rendered segment indexes in source traversal order. */
10609
+ getPathSegmentIndices(dataIndex) {
10610
+ const segmentTypes = this.attributes.segmentTypes;
10611
+ const rowStart = this.vertexStarts[dataIndex];
10612
+ const rowEnd = Math.min(
10613
+ this.vertexStarts[dataIndex + 1] ?? this.instanceCount,
10614
+ this.instanceCount
10615
+ );
10616
+ const result = [];
10617
+ for (let segmentIndex = rowStart; segmentIndex < rowEnd - 1; segmentIndex++) {
10618
+ if ((segmentTypes[segmentIndex] & INVALID) === 0) {
10619
+ result.push(segmentIndex);
10620
+ }
10621
+ }
10622
+ if (result.length && (segmentTypes[rowStart] & INVALID) !== 0) {
10623
+ result.unshift(result.pop());
10624
+ }
10625
+ return result;
10626
+ }
10602
10627
  /* Implement base Tesselator interface */
10603
10628
  getGeometryFromBuffer(buffer) {
10604
10629
  if (this.normalize || this.opts.isWebGPU) {
@@ -10794,6 +10819,9 @@ struct Varyings {
10794
10819
  @location(5) vJointType: f32,
10795
10820
  // Location 6 is reserved for TripsLayer's injected vTime varying.
10796
10821
  @location(7) clipCoordinates: vec2<f32>,
10822
+ #ifdef DASH_ENABLED
10823
+ @location(8) vPathBounds: vec2<f32>,
10824
+ #endif
10797
10825
  };
10798
10826
 
10799
10827
  fn flipIfTrue(flag: bool) -> f32 {
@@ -10808,11 +10836,34 @@ fn clipLine(position: vec4<f32>, refPosition: vec4<f32>) -> vec4<f32> {
10808
10836
  return position;
10809
10837
  }
10810
10838
 
10839
+ #ifdef DASH_ENABLED
10840
+ // Return the visible interval of the original segment before clipLine moves either endpoint.
10841
+ fn getClippedPathRange(startW: f32, endW: f32) -> vec2<f32> {
10842
+ let startClipped = startW < EPSILON;
10843
+ let endClipped = endW < EPSILON;
10844
+ if (startClipped && endClipped) {
10845
+ return vec2<f32>(0.0, 0.0);
10846
+ }
10847
+ if (startClipped || endClipped) {
10848
+ let intersection = clamp((EPSILON - startW) / (endW - startW), 0.0, 1.0);
10849
+ if (startClipped) {
10850
+ return vec2<f32>(intersection, 1.0);
10851
+ }
10852
+ return vec2<f32>(0.0, intersection);
10853
+ }
10854
+ return vec2<f32>(0.0, 1.0);
10855
+ }
10856
+ #endif
10857
+
10811
10858
  fn getLineJoinOffset(
10812
10859
  prevPoint: vec3<f32>,
10813
10860
  currPoint: vec3<f32>,
10814
10861
  nextPoint: vec3<f32>,
10815
10862
  width: vec2<f32>,
10863
+ #ifdef DASH_ENABLED
10864
+ sourcePathLength: f32,
10865
+ sourcePathRange: vec2<f32>,
10866
+ #endif
10816
10867
  #ifdef ANTIALIASING
10817
10868
  coverageScale: f32,
10818
10869
  #endif
@@ -10849,7 +10900,43 @@ fn getLineJoinOffset(
10849
10900
  let miterVec = vec2<f32>(-tangent.y, tangent.x);
10850
10901
  let dir = select(dirB, dirA, isEnd);
10851
10902
  let perp = select(perpB, perpA, isEnd);
10903
+ #ifdef DASH_ENABLED
10904
+ let segmentLength2D = select(lenB, lenA, isEnd);
10905
+
10906
+ // Extrusion happens in the XY plane, so segmentLength2D is a 2D length and pathPosition.y
10907
+ // below measures 2D distance along the segment. For a path that also moves in Z the true
10908
+ // arc length is longer by this ratio. Scaling pathLength and pathPosition.y by it makes
10909
+ // the coordinate measure real 3D distance while leaving the joint tests unchanged, since
10910
+ // they compare the two against each other and both are scaled alike. Billboard mode
10911
+ // extrudes in clip space, where the perspective divide has already reduced the segment to
10912
+ // its screen projection, so its complete common-space length is supplied by the caller.
10913
+ // Mirrors path-layer-vertex.glsl.ts.
10914
+ let currDelta3 = select(deltaB3, deltaA3, isEnd);
10915
+ let currLength2D = length(currDelta3.xy);
10916
+ // Do not clamp a valid denominator to EPSILON: high-zoom Web Mercator deltas are often
10917
+ // smaller than that in common space, and changing their scale corrupts even flat paths.
10918
+ let safeLength2D = select(1.0, currLength2D, currLength2D > 0.0);
10919
+ var arcLengthRatio = 1.0;
10920
+ var pathPositionOffset = 0.0;
10921
+ var pathLength = segmentLength2D;
10922
+ if (path.billboard != 0.0) {
10923
+ // clipLine may shorten the visible screen-space segment. Preserve the corresponding interval
10924
+ // of the complete common-space arclength instead of compressing the full dash period into the
10925
+ // visible span. Keep pathLength complete so justification is stable as the camera clips it.
10926
+ let visiblePathLength = sourcePathLength * (sourcePathRange.y - sourcePathRange.x);
10927
+ arcLengthRatio = 0.0;
10928
+ if (segmentLength2D > 0.0) {
10929
+ arcLengthRatio = visiblePathLength / segmentLength2D;
10930
+ }
10931
+ pathPositionOffset = sourcePathLength * sourcePathRange.x;
10932
+ pathLength = sourcePathLength;
10933
+ } else if (currLength2D > 0.0) {
10934
+ arcLengthRatio = length(currDelta3) / safeLength2D;
10935
+ pathLength = segmentLength2D * arcLengthRatio;
10936
+ }
10937
+ #else
10852
10938
  let pathLength = select(lenB, lenA, isEnd);
10939
+ #endif
10853
10940
 
10854
10941
  let sinHalfA = abs(dot(miterVec, perp));
10855
10942
  let cosHalfA = abs(dot(dirA, miterVec));
@@ -10896,7 +10983,11 @@ fn getLineJoinOffset(
10896
10983
  #endif
10897
10984
  let pathPosition = vec2<f32>(
10898
10985
  dot(offsetFromStartOfPath, perp),
10986
+ #ifdef DASH_ENABLED
10987
+ pathPositionOffset + dot(offsetFromStartOfPath, dir) * arcLengthRatio
10988
+ #else
10899
10989
  dot(offsetFromStartOfPath, dir)
10990
+ #endif
10900
10991
  );
10901
10992
  let isValid = step(f32(instanceTypes), 3.5);
10902
10993
  #ifdef ANTIALIASING
@@ -10955,15 +11046,38 @@ fn vertexMain(attributes: Attributes) -> Varyings {
10955
11046
  ) / 2.0;
10956
11047
 
10957
11048
  if (path.billboard != 0.0) {
10958
- var prevPositionScreen = project_position_to_clipspace(prevPosition, prevPosition64Low, ZERO_OFFSET);
11049
+ #ifdef DASH_ENABLED
11050
+ let prevProjection = project_position_to_clipspace_and_commonspace(
11051
+ prevPosition, prevPosition64Low, ZERO_OFFSET
11052
+ );
11053
+ let nextProjection = project_position_to_clipspace_and_commonspace(
11054
+ nextPosition, nextPosition64Low, ZERO_OFFSET
11055
+ );
11056
+ let prevPositionCommon = prevProjection.commonPosition.xyz;
11057
+ let nextPositionCommon = nextProjection.commonPosition.xyz;
11058
+ var prevPositionScreen = prevProjection.clipPosition;
11059
+ var nextPositionScreen = nextProjection.clipPosition;
11060
+ #else
11061
+ var prevPositionScreen = project_position_to_clipspace(
11062
+ prevPosition, prevPosition64Low, ZERO_OFFSET
11063
+ );
11064
+ var nextPositionScreen = project_position_to_clipspace(
11065
+ nextPosition, nextPosition64Low, ZERO_OFFSET
11066
+ );
11067
+ #endif
10959
11068
  let currProjection = project_position_to_clipspace_and_commonspace(
10960
- currPosition,
10961
- currPosition64Low,
10962
- ZERO_OFFSET
11069
+ currPosition, currPosition64Low, ZERO_OFFSET
10963
11070
  );
10964
11071
  geometry.position = currProjection.commonPosition;
10965
11072
  var currPositionScreen = currProjection.clipPosition;
10966
- var nextPositionScreen = project_position_to_clipspace(nextPosition, nextPosition64Low, ZERO_OFFSET);
11073
+ #ifdef DASH_ENABLED
11074
+ let currPositionCommon = currProjection.commonPosition.xyz;
11075
+ let sourcePathStartScreen = mix(currPositionScreen, prevPositionScreen, isEnd);
11076
+ let sourcePathEndScreen = mix(nextPositionScreen, currPositionScreen, isEnd);
11077
+ let billboardPathRange = getClippedPathRange(
11078
+ sourcePathStartScreen.w, sourcePathEndScreen.w
11079
+ );
11080
+ #endif
10967
11081
 
10968
11082
  prevPositionScreen = clipLine(prevPositionScreen, currPositionScreen);
10969
11083
  nextPositionScreen = clipLine(nextPositionScreen, currPositionScreen);
@@ -10975,18 +11089,39 @@ fn vertexMain(attributes: Attributes) -> Varyings {
10975
11089
  (widthPixels + 0.5 / project.devicePixelRatio) / max(widthPixels, 1e-6),
10976
11090
  widthPixels > 0.0
10977
11091
  );
11092
+ #endif
11093
+ #ifdef DASH_ENABLED
11094
+ let currentDeltaCommon = select(
11095
+ nextPositionCommon - currPositionCommon,
11096
+ currPositionCommon - prevPositionCommon,
11097
+ isEnd > 0.0
11098
+ );
11099
+ let billboardPathLength = select(
11100
+ 0.0,
11101
+ length(currentDeltaCommon) * project.scale / (widthPixels * project.focalDistance),
11102
+ widthPixels > 0.0
11103
+ );
10978
11104
  #endif
10979
11105
  let join = getLineJoinOffset(
10980
11106
  prevPositionScreen.xyz / prevPositionScreen.w,
10981
11107
  currPositionScreen.xyz / currPositionScreen.w,
10982
11108
  nextPositionScreen.xyz / nextPositionScreen.w,
10983
11109
  project_pixel_size_to_clipspace(vec2<f32>(widthPixels, widthPixels)),
11110
+ #ifdef DASH_ENABLED
11111
+ billboardPathLength,
11112
+ billboardPathRange,
11113
+ #endif
10984
11114
  #ifdef ANTIALIASING
10985
11115
  coverageScale,
10986
11116
  #endif
10987
11117
  attributes.positions,
10988
11118
  attributes.instanceTypes
10989
11119
  );
11120
+ #ifdef DASH_ENABLED
11121
+ // Phase and justification use the complete source segment, while cap and joint coverage
11122
+ // must still recognize the endpoints moved by clipLine.
11123
+ varyings.vPathBounds = billboardPathLength * billboardPathRange;
11124
+ #endif
10990
11125
 
10991
11126
  geometry.uv = join.pathPosition;
10992
11127
  varyings.position = vec4<f32>(
@@ -11019,12 +11154,19 @@ fn vertexMain(attributes: Attributes) -> Varyings {
11019
11154
  currPositionCommon,
11020
11155
  nextPositionCommon,
11021
11156
  width,
11157
+ #ifdef DASH_ENABLED
11158
+ 1.0,
11159
+ vec2<f32>(0.0, 1.0),
11160
+ #endif
11022
11161
  #ifdef ANTIALIASING
11023
11162
  coverageScale,
11024
11163
  #endif
11025
11164
  attributes.positions,
11026
11165
  attributes.instanceTypes
11027
11166
  );
11167
+ #ifdef DASH_ENABLED
11168
+ varyings.vPathBounds = vec2<f32>(0.0, join.pathLength);
11169
+ #endif
11028
11170
 
11029
11171
  geometry.position = vec4<f32>(currPositionCommon + join.offset, 1.0);
11030
11172
  geometry.uv = join.pathPosition;
@@ -11055,7 +11197,13 @@ fn fragmentMain(varyings: Varyings) -> @location(0) vec4<f32> {
11055
11197
  // bounded by the corner offset, everywhere else by the edge of the stroke. Dividing by the
11056
11198
  // screen-space derivative converts the distance to the boundary into device pixels, which stays
11057
11199
  // correct under perspective foreshortening and under extensions that rescale the stroke.
11200
+ #ifdef DASH_ENABLED
11201
+ let isCorner =
11202
+ varyings.vPathPosition.y < varyings.vPathBounds.x ||
11203
+ varyings.vPathPosition.y > varyings.vPathBounds.y;
11204
+ #else
11058
11205
  let isCorner = varyings.vPathPosition.y < 0.0 || varyings.vPathPosition.y > varyings.vPathLength;
11206
+ #endif
11059
11207
  let isRound = varyings.vJointType > 0.5;
11060
11208
 
11061
11209
  // Distance to the silhouette in device pixels, from the derivative of the coordinate that
@@ -11066,7 +11214,13 @@ fn fragmentMain(varyings: Varyings) -> @location(0) vec4<f32> {
11066
11214
  // Both evaluated so each derivative stays on one field across the corner/body boundary
11067
11215
  let bodyPixels = (1.0 - bodyCoord) / max(fwidth(bodyCoord), 1e-6);
11068
11216
  let cornerPixels = (1.0 - cornerCoord) / max(fwidth(cornerCoord), 1e-6);
11217
+ #ifdef PATH_STYLE_OFFSET
11218
+ // Rounded corners still intersect the stroke-width envelope. Extensions may remap
11219
+ // vPathPosition.x independently of vCornerOffset, as PathStyleExtension does for offsets.
11220
+ let edgePixels = select(bodyPixels, min(cornerPixels, bodyPixels), isRound && isCorner);
11221
+ #else
11069
11222
  let edgePixels = select(bodyPixels, cornerPixels, isRound && isCorner);
11223
+ #endif
11070
11224
 
11071
11225
  // Fragments outside the coverage ramp must not write depth or picking colors.
11072
11226
  if (edgePixels <= -SMOOTH_EDGE_RADIUS) {
@@ -11084,11 +11238,18 @@ fn fragmentMain(varyings: Varyings) -> @location(0) vec4<f32> {
11084
11238
  // Feather one device pixel across the width only, before premultiplication. edgePixels is a
11085
11239
  // signed device-pixel distance and SMOOTH_EDGE_RADIUS is 0.5, so this ramps across one pixel.
11086
11240
  color.a *= smoothedge(0.0, edgePixels);
11241
+ #else
11242
+ #ifdef DASH_ENABLED
11243
+ if (
11244
+ varyings.vPathPosition.y < varyings.vPathBounds.x ||
11245
+ varyings.vPathPosition.y > varyings.vPathBounds.y
11246
+ ) {
11087
11247
  #else
11088
11248
  if (
11089
11249
  varyings.vPathPosition.y < 0.0 ||
11090
11250
  varyings.vPathPosition.y > varyings.vPathLength
11091
11251
  ) {
11252
+ #endif
11092
11253
  if (varyings.vJointType > 0.5 && length(varyings.vCornerOffset) > 1.0) {
11093
11254
  discard;
11094
11255
  }
@@ -11141,6 +11302,9 @@ out float vMiterLength;
11141
11302
  out vec2 vPathPosition;
11142
11303
  out float vPathLength;
11143
11304
  out float vJointType;
11305
+ #ifdef DASH_ENABLED
11306
+ out vec2 vPathBounds;
11307
+ #endif
11144
11308
 
11145
11309
  const float EPSILON = 0.001;
11146
11310
  const vec3 ZERO_OFFSET = vec3(0.0);
@@ -11153,6 +11317,9 @@ float flipIfTrue(bool flag) {
11153
11317
  vec3 getLineJoinOffset(
11154
11318
  vec3 prevPoint, vec3 currPoint, vec3 nextPoint,
11155
11319
  vec2 width
11320
+ #ifdef DASH_ENABLED
11321
+ , float sourcePathLength, vec2 sourcePathRange
11322
+ #endif
11156
11323
  #ifdef ANTIALIASING
11157
11324
  , float coverageScale
11158
11325
  #endif
@@ -11195,6 +11362,37 @@ vec3 getLineJoinOffset(
11195
11362
  // length of the segment
11196
11363
  float L = isEnd ? lenA : lenB;
11197
11364
 
11365
+ #ifdef DASH_ENABLED
11366
+ // Extrusion happens in the XY plane, so L above is a 2D length and vPathPosition.y below
11367
+ // measures 2D distance along the segment. For a path that also moves in Z the true arc
11368
+ // length is longer by this ratio. Scaling vPathLength and vPathPosition.y by it makes the
11369
+ // coordinate measure real 3D distance - which is what CPU-side dash offsets accumulate -
11370
+ // while leaving the joint tests in the fragment shader unchanged, since they compare the
11371
+ // two against each other and both are scaled alike.
11372
+ // Billboard mode extrudes in clip space, where the perspective divide has already reduced
11373
+ // the segment to its screen projection, so its complete common-space length is supplied by
11374
+ // the caller.
11375
+ vec3 currDelta3 = isEnd ? deltaA3 : deltaB3;
11376
+ float currLength2D = length(currDelta3.xy);
11377
+ float arcLengthRatio = 1.0;
11378
+ float pathPositionOffset = 0.0;
11379
+ float pathLength = L;
11380
+ if (path.billboard) {
11381
+ // clipLine may shorten the visible screen-space segment. Preserve the corresponding interval
11382
+ // of the complete common-space arclength instead of compressing the full dash period into the
11383
+ // visible span. Keep pathLength complete so justification is stable as the camera clips it.
11384
+ float visiblePathLength = sourcePathLength * (sourcePathRange.y - sourcePathRange.x);
11385
+ arcLengthRatio = L > 0.0 ? visiblePathLength / L : 0.0;
11386
+ pathPositionOffset = sourcePathLength * sourcePathRange.x;
11387
+ pathLength = sourcePathLength;
11388
+ } else if (currLength2D > 0.0) {
11389
+ // Do not clamp a valid denominator to EPSILON: high-zoom Web Mercator deltas are often
11390
+ // smaller than that in common space, and changing their scale corrupts even flat paths.
11391
+ arcLengthRatio = length(currDelta3) / currLength2D;
11392
+ pathLength = L * arcLengthRatio;
11393
+ }
11394
+ #endif
11395
+
11198
11396
  // A = angle of the corner
11199
11397
  float sinHalfA = abs(dot(miterVec, perp));
11200
11398
  float cosHalfA = abs(dot(dirA, miterVec));
@@ -11236,7 +11434,11 @@ vec3 getLineJoinOffset(
11236
11434
  // The physical stroke still ends at offsetVec; the scaled coordinates and vertices only extend
11237
11435
  // its rasterized envelope to include the outer half of the centered coverage ramp.
11238
11436
  vec2 coverageOffsetVec = offsetVec * coverageScale;
11437
+ #ifdef DASH_ENABLED
11438
+ vPathLength = pathLength;
11439
+ #else
11239
11440
  vPathLength = L;
11441
+ #endif
11240
11442
  vCornerOffset = coverageOffsetVec;
11241
11443
  vMiterLength = dot(vCornerOffset, miterVec * turnDirection);
11242
11444
  vMiterLength = isCap ? isJoint : vMiterLength;
@@ -11244,7 +11446,11 @@ vec3 getLineJoinOffset(
11244
11446
  vec2 offsetFromStartOfPath = coverageOffsetVec + deltaA * float(isEnd);
11245
11447
  vPathPosition = vec2(
11246
11448
  dot(offsetFromStartOfPath, perp),
11449
+ #ifdef DASH_ENABLED
11450
+ pathPositionOffset + dot(offsetFromStartOfPath, dir) * arcLengthRatio
11451
+ #else
11247
11452
  dot(offsetFromStartOfPath, dir)
11453
+ #endif
11248
11454
  );
11249
11455
  geometry.uv = vPathPosition;
11250
11456
 
@@ -11252,7 +11458,11 @@ vec3 getLineJoinOffset(
11252
11458
  vec3 offset = vec3(coverageOffsetVec * width * isValid, 0.0);
11253
11459
  #else
11254
11460
  // Generate variables for fragment shader
11461
+ #ifdef DASH_ENABLED
11462
+ vPathLength = pathLength;
11463
+ #else
11255
11464
  vPathLength = L;
11465
+ #endif
11256
11466
  vCornerOffset = offsetVec;
11257
11467
  vMiterLength = dot(vCornerOffset, miterVec * turnDirection);
11258
11468
  vMiterLength = isCap ? isJoint : vMiterLength;
@@ -11260,7 +11470,11 @@ vec3 getLineJoinOffset(
11260
11470
  vec2 offsetFromStartOfPath = vCornerOffset + deltaA * float(isEnd);
11261
11471
  vPathPosition = vec2(
11262
11472
  dot(offsetFromStartOfPath, perp),
11473
+ #ifdef DASH_ENABLED
11474
+ pathPositionOffset + dot(offsetFromStartOfPath, dir) * arcLengthRatio
11475
+ #else
11263
11476
  dot(offsetFromStartOfPath, dir)
11477
+ #endif
11264
11478
  );
11265
11479
  geometry.uv = vPathPosition;
11266
11480
 
@@ -11282,6 +11496,22 @@ void clipLine(inout vec4 position, vec4 refPosition) {
11282
11496
  }
11283
11497
  }
11284
11498
 
11499
+ #ifdef DASH_ENABLED
11500
+ // Return the visible interval of the original segment before clipLine moves either endpoint.
11501
+ vec2 getClippedPathRange(float startW, float endW) {
11502
+ bool startClipped = startW < EPSILON;
11503
+ bool endClipped = endW < EPSILON;
11504
+ if (startClipped && endClipped) {
11505
+ return vec2(0.0);
11506
+ }
11507
+ if (startClipped || endClipped) {
11508
+ float intersection = clamp((EPSILON - startW) / (endW - startW), 0.0, 1.0);
11509
+ return startClipped ? vec2(intersection, 1.0) : vec2(0.0, intersection);
11510
+ }
11511
+ return vec2(0.0, 1.0);
11512
+ }
11513
+ #endif
11514
+
11285
11515
  void main() {
11286
11516
  geometry.pickingColor = picking_getPickingColorFromIndex(rowIndexes);
11287
11517
 
@@ -11306,9 +11536,35 @@ void main() {
11306
11536
 
11307
11537
  if (path.billboard) {
11308
11538
  // Extrude in clipspace
11309
- vec4 prevPositionScreen = project_position_to_clipspace(prevPosition, prevPosition64Low, ZERO_OFFSET);
11539
+ #ifdef DASH_ENABLED
11540
+ vec4 prevPositionCommon;
11541
+ vec4 nextPositionCommon;
11542
+ vec4 prevPositionScreen = project_position_to_clipspace(
11543
+ prevPosition, prevPosition64Low, ZERO_OFFSET, prevPositionCommon
11544
+ );
11545
+ #else
11546
+ vec4 prevPositionScreen = project_position_to_clipspace(
11547
+ prevPosition, prevPosition64Low, ZERO_OFFSET
11548
+ );
11549
+ #endif
11310
11550
  vec4 currPositionScreen = project_position_to_clipspace(currPosition, currPosition64Low, ZERO_OFFSET, geometry.position);
11311
- vec4 nextPositionScreen = project_position_to_clipspace(nextPosition, nextPosition64Low, ZERO_OFFSET);
11551
+ #ifdef DASH_ENABLED
11552
+ vec4 nextPositionScreen = project_position_to_clipspace(
11553
+ nextPosition, nextPosition64Low, ZERO_OFFSET, nextPositionCommon
11554
+ );
11555
+ #else
11556
+ vec4 nextPositionScreen = project_position_to_clipspace(
11557
+ nextPosition, nextPosition64Low, ZERO_OFFSET
11558
+ );
11559
+ #endif
11560
+
11561
+ #ifdef DASH_ENABLED
11562
+ vec4 sourcePathStartScreen = mix(currPositionScreen, prevPositionScreen, isEnd);
11563
+ vec4 sourcePathEndScreen = mix(nextPositionScreen, currPositionScreen, isEnd);
11564
+ vec2 billboardPathRange = getClippedPathRange(
11565
+ sourcePathStartScreen.w, sourcePathEndScreen.w
11566
+ );
11567
+ #endif
11312
11568
 
11313
11569
  clipLine(prevPositionScreen, currPositionScreen);
11314
11570
  clipLine(nextPositionScreen, currPositionScreen);
@@ -11323,16 +11579,34 @@ void main() {
11323
11579
  : 1.0;
11324
11580
  #endif
11325
11581
 
11582
+ #ifdef DASH_ENABLED
11583
+ vec3 currentDeltaCommon = isEnd > 0.0
11584
+ ? geometry.position.xyz - prevPositionCommon.xyz
11585
+ : nextPositionCommon.xyz - geometry.position.xyz;
11586
+ float billboardPathLength = width.x > 0.0
11587
+ ? length(currentDeltaCommon) * project.scale / (width.x * project.focalDistance)
11588
+ : 0.0;
11589
+ #endif
11590
+
11326
11591
  vec3 offset = getLineJoinOffset(
11327
11592
  prevPositionScreen.xyz / prevPositionScreen.w,
11328
11593
  currPositionScreen.xyz / currPositionScreen.w,
11329
11594
  nextPositionScreen.xyz / nextPositionScreen.w,
11330
11595
  project_pixel_size_to_clipspace(width.xy)
11596
+ #ifdef DASH_ENABLED
11597
+ ,
11598
+ billboardPathLength, billboardPathRange
11599
+ #endif
11331
11600
  #ifdef ANTIALIASING
11332
11601
  ,
11333
11602
  coverageScale
11334
11603
  #endif
11335
11604
  );
11605
+ #ifdef DASH_ENABLED
11606
+ // Phase and justification use the complete source segment, while cap and joint coverage
11607
+ // must still recognize the endpoints moved by clipLine.
11608
+ vPathBounds = billboardPathLength * billboardPathRange;
11609
+ #endif
11336
11610
 
11337
11611
  DECKGL_FILTER_GL_POSITION(currPositionScreen, geometry);
11338
11612
  gl_Position = vec4(currPositionScreen.xyz + offset * currPositionScreen.w, currPositionScreen.w);
@@ -11353,10 +11627,16 @@ void main() {
11353
11627
 
11354
11628
  vec3 offset = getLineJoinOffset(
11355
11629
  prevPosition, currPosition, nextPosition, width.xy
11630
+ #ifdef DASH_ENABLED
11631
+ , 1.0, vec2(0.0, 1.0)
11632
+ #endif
11356
11633
  #ifdef ANTIALIASING
11357
11634
  , coverageScale
11358
11635
  #endif
11359
11636
  );
11637
+ #ifdef DASH_ENABLED
11638
+ vPathBounds = vec2(0.0, vPathLength);
11639
+ #endif
11360
11640
  geometry.position = vec4(currPosition + offset, 1.0);
11361
11641
  gl_Position = project_common_position_to_clipspace(geometry.position);
11362
11642
  DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
@@ -11382,6 +11662,9 @@ in float vMiterLength;
11382
11662
  in vec2 vPathPosition;
11383
11663
  in float vPathLength;
11384
11664
  in float vJointType;
11665
+ #ifdef DASH_ENABLED
11666
+ in vec2 vPathBounds;
11667
+ #endif
11385
11668
 
11386
11669
  out vec4 fragColor;
11387
11670
 
@@ -11389,7 +11672,11 @@ void main(void) {
11389
11672
  geometry.uv = vPathPosition;
11390
11673
 
11391
11674
  #ifdef ANTIALIASING
11675
+ #ifdef DASH_ENABLED
11676
+ bool isCorner = vPathPosition.y < vPathBounds.x || vPathPosition.y > vPathBounds.y;
11677
+ #else
11392
11678
  bool isCorner = vPathPosition.y < 0.0 || vPathPosition.y > vPathLength;
11679
+ #endif
11393
11680
  bool isRound = vJointType > 0.5;
11394
11681
 
11395
11682
  // Distance to the silhouette in device pixels, from the derivative of the coordinate that
@@ -11400,7 +11687,13 @@ void main(void) {
11400
11687
  // Both evaluated so each derivative stays on one field across the corner/body boundary
11401
11688
  float bodyPixels = (1.0 - bodyCoord) / max(fwidth(bodyCoord), 1e-6);
11402
11689
  float cornerPixels = (1.0 - cornerCoord) / max(fwidth(cornerCoord), 1e-6);
11690
+ #ifdef PATH_STYLE_OFFSET
11691
+ // Rounded corners still intersect the stroke-width envelope. Extensions may remap
11692
+ // vPathPosition.x independently of vCornerOffset, as PathStyleExtension does for offsets.
11693
+ float edgePixels = isRound && isCorner ? min(cornerPixels, bodyPixels) : bodyPixels;
11694
+ #else
11403
11695
  float edgePixels = isRound && isCorner ? cornerPixels : bodyPixels;
11696
+ #endif
11404
11697
 
11405
11698
  // Fragments outside the coverage ramp must not write depth or picking colors.
11406
11699
  if (edgePixels <= -SMOOTH_EDGE_RADIUS) {
@@ -11419,8 +11712,12 @@ void main(void) {
11419
11712
  // edgePixels is a signed device-pixel distance, and SMOOTH_EDGE_RADIUS is 0.5, so smoothedge
11420
11713
  // ramps across exactly one pixel centered on the edge.
11421
11714
  fragColor.a *= smoothedge(0.0, edgePixels);
11715
+ #else
11716
+ #ifdef DASH_ENABLED
11717
+ if (vPathPosition.y < vPathBounds.x || vPathPosition.y > vPathBounds.y) {
11422
11718
  #else
11423
11719
  if (vPathPosition.y < 0.0 || vPathPosition.y > vPathLength) {
11720
+ #endif
11424
11721
  // if joint is rounded, test distance from the corner
11425
11722
  if (vJointType > 0.5 && length(vCornerOffset) > 1.0) {
11426
11723
  discard;
@@ -11461,6 +11758,18 @@ var ATTRIBUTE_TRANSITION = {
11461
11758
  return chunk.length ? chunk.subarray(chunk.length - value.length) : value;
11462
11759
  }
11463
11760
  };
11761
+ function getPathProjectionScale(viewport) {
11762
+ if (viewport.isGeospatial) {
11763
+ return null;
11764
+ }
11765
+ const { unitsPerMeter: unitsPerMeter2 } = viewport.distanceScales;
11766
+ return [unitsPerMeter2[0], unitsPerMeter2[1], unitsPerMeter2[2]];
11767
+ }
11768
+ function pathProjectionScalesEqual(left, right) {
11769
+ return left === right || Boolean(
11770
+ left && right && left.length === right.length && left.every((value, index) => value === right[index])
11771
+ );
11772
+ }
11464
11773
  var PathLayer = class extends Layer {
11465
11774
  getShaders() {
11466
11775
  const { antialiasing } = this.props;
@@ -11488,6 +11797,43 @@ var PathLayer = class extends Layer {
11488
11797
  }
11489
11798
  return (_a = this.getAttributeManager()) == null ? void 0 : _a.getBounds(["vertexPositions"]);
11490
11799
  }
11800
+ getPathProjectionScale(viewport) {
11801
+ var _a;
11802
+ const coordinateSystem = this.props.coordinateSystem;
11803
+ const hasDashMetrics = Boolean((_a = this.getAttributeManager()) == null ? void 0 : _a.getAttributes().instanceDashOffsets);
11804
+ if (!hasDashMetrics) {
11805
+ return null;
11806
+ }
11807
+ const trackViewportScale = viewport instanceof WebMercatorViewport && viewport.zoom >= 12 && // Offset coordinate systems use their fixed coordinateOrigin as the projection origin.
11808
+ // Default/lnglat and preprojected Cartesian paths follow the viewport center instead.
11809
+ (coordinateSystem === "default" || coordinateSystem === "lnglat" || coordinateSystem === "cartesian");
11810
+ if (trackViewportScale) {
11811
+ const projectUniforms = project_default.getUniforms({
11812
+ viewport,
11813
+ coordinateSystem,
11814
+ coordinateOrigin: this.props.coordinateOrigin,
11815
+ autoWrapLongitude: this.wrapLongitude
11816
+ });
11817
+ return [
11818
+ viewport.projectionMode,
11819
+ projectUniforms.coordinateOrigin[1],
11820
+ projectUniforms.commonOrigin[1],
11821
+ ...projectUniforms.commonUnitsPerWorldUnit,
11822
+ ...projectUniforms.commonUnitsPerWorldUnit2,
11823
+ projectUniforms.commonUnitsPerMeter[2]
11824
+ ];
11825
+ }
11826
+ const projectionScale = getPathProjectionScale(viewport);
11827
+ return projectionScale ? [viewport.projectionMode, ...projectionScale] : [viewport.projectionMode];
11828
+ }
11829
+ shouldUpdateState(params) {
11830
+ var _a, _b;
11831
+ const { viewport } = this.context;
11832
+ return super.shouldUpdateState(params) || ((_a = this.state) == null ? void 0 : _a.tessellationResolution) !== viewport.resolution || !pathProjectionScalesEqual(
11833
+ (_b = this.state) == null ? void 0 : _b.pathProjectionScale,
11834
+ this.getPathProjectionScale(viewport)
11835
+ );
11836
+ }
11491
11837
  initializeState() {
11492
11838
  const noAlloc = true;
11493
11839
  const isWebGPU = this.context.device.type === "webgpu";
@@ -11578,7 +11924,9 @@ var PathLayer = class extends Layer {
11578
11924
  pathTesselator: new PathTesselator({
11579
11925
  fp64: this.use64bitPositions(),
11580
11926
  isWebGPU
11581
- })
11927
+ }),
11928
+ tessellationResolution: this.context.viewport.resolution,
11929
+ pathProjectionScale: this.getPathProjectionScale(this.context.viewport)
11582
11930
  });
11583
11931
  }
11584
11932
  updateState(params) {
@@ -11586,7 +11934,16 @@ var PathLayer = class extends Layer {
11586
11934
  super.updateState(params);
11587
11935
  const { props, oldProps, changeFlags } = params;
11588
11936
  const attributeManager = this.getAttributeManager();
11589
- const geometryChanged = changeFlags.dataChanged || changeFlags.updateTriggersChanged && (changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getPath);
11937
+ const { viewport } = this.context;
11938
+ const tessellationResolutionChanged = this.state.tessellationResolution !== viewport.resolution;
11939
+ const pathProjectionScale = this.getPathProjectionScale(viewport);
11940
+ const pathProjectionScaleChanged = !pathProjectionScalesEqual(
11941
+ this.state.pathProjectionScale,
11942
+ pathProjectionScale
11943
+ );
11944
+ const getPathChanged = changeFlags.updateTriggersChanged && (changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getPath);
11945
+ const geometryConfigurationChanged = getPathChanged || props._pathType !== oldProps._pathType || props.positionFormat !== oldProps.positionFormat || props.wrapLongitude !== oldProps.wrapLongitude || tessellationResolutionChanged;
11946
+ const geometryChanged = changeFlags.dataChanged || geometryConfigurationChanged;
11590
11947
  if (geometryChanged) {
11591
11948
  const { pathTesselator } = this.state;
11592
11949
  const buffers = props.data.attributes || {};
@@ -11600,16 +11957,24 @@ var PathLayer = class extends Layer {
11600
11957
  positionFormat: props.positionFormat,
11601
11958
  wrapLongitude: props.wrapLongitude,
11602
11959
  // TODO - move the flag out of the viewport
11603
- resolution: this.context.viewport.resolution,
11604
- dataChanged: changeFlags.dataChanged
11960
+ resolution: viewport.resolution,
11961
+ // A partial data diff is only valid while normalization inputs remain unchanged.
11962
+ dataChanged: geometryConfigurationChanged ? void 0 : changeFlags.dataChanged
11605
11963
  });
11606
11964
  this.setState({
11607
11965
  numInstances: pathTesselator.instanceCount,
11608
- startIndices: pathTesselator.vertexStarts
11966
+ startIndices: pathTesselator.vertexStarts,
11967
+ tessellationResolution: viewport.resolution,
11968
+ pathProjectionScale
11609
11969
  });
11610
- if (!changeFlags.dataChanged) {
11970
+ if (!changeFlags.dataChanged || geometryConfigurationChanged) {
11611
11971
  attributeManager.invalidateAll();
11972
+ } else if (pathProjectionScaleChanged) {
11973
+ attributeManager.invalidate("instanceDashOffsets");
11612
11974
  }
11975
+ } else if (pathProjectionScaleChanged) {
11976
+ this.setState({ pathProjectionScale });
11977
+ attributeManager.invalidate("instanceDashOffsets");
11613
11978
  }
11614
11979
  if (changeFlags.extensionsChanged || props.antialiasing !== oldProps.antialiasing) {
11615
11980
  (_a = this.state.model) == null ? void 0 : _a.destroy();