@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.
@@ -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: [
@@ -10515,6 +10521,25 @@ var PathTesselator = class extends Tesselator {
10515
10521
  get(attributeName) {
10516
10522
  return this.attributes[attributeName];
10517
10523
  }
10524
+ /** Get rendered segment indexes in source traversal order. */
10525
+ getPathSegmentIndices(dataIndex) {
10526
+ const segmentTypes = this.attributes.segmentTypes;
10527
+ const rowStart = this.vertexStarts[dataIndex];
10528
+ const rowEnd = Math.min(
10529
+ this.vertexStarts[dataIndex + 1] ?? this.instanceCount,
10530
+ this.instanceCount
10531
+ );
10532
+ const result = [];
10533
+ for (let segmentIndex = rowStart; segmentIndex < rowEnd - 1; segmentIndex++) {
10534
+ if ((segmentTypes[segmentIndex] & INVALID) === 0) {
10535
+ result.push(segmentIndex);
10536
+ }
10537
+ }
10538
+ if (result.length && (segmentTypes[rowStart] & INVALID) !== 0) {
10539
+ result.unshift(result.pop());
10540
+ }
10541
+ return result;
10542
+ }
10518
10543
  /* Implement base Tesselator interface */
10519
10544
  getGeometryFromBuffer(buffer) {
10520
10545
  if (this.normalize || this.opts.isWebGPU) {
@@ -10710,6 +10735,9 @@ struct Varyings {
10710
10735
  @location(5) vJointType: f32,
10711
10736
  // Location 6 is reserved for TripsLayer's injected vTime varying.
10712
10737
  @location(7) clipCoordinates: vec2<f32>,
10738
+ #ifdef DASH_ENABLED
10739
+ @location(8) vPathBounds: vec2<f32>,
10740
+ #endif
10713
10741
  };
10714
10742
 
10715
10743
  fn flipIfTrue(flag: bool) -> f32 {
@@ -10724,11 +10752,34 @@ fn clipLine(position: vec4<f32>, refPosition: vec4<f32>) -> vec4<f32> {
10724
10752
  return position;
10725
10753
  }
10726
10754
 
10755
+ #ifdef DASH_ENABLED
10756
+ // Return the visible interval of the original segment before clipLine moves either endpoint.
10757
+ fn getClippedPathRange(startW: f32, endW: f32) -> vec2<f32> {
10758
+ let startClipped = startW < EPSILON;
10759
+ let endClipped = endW < EPSILON;
10760
+ if (startClipped && endClipped) {
10761
+ return vec2<f32>(0.0, 0.0);
10762
+ }
10763
+ if (startClipped || endClipped) {
10764
+ let intersection = clamp((EPSILON - startW) / (endW - startW), 0.0, 1.0);
10765
+ if (startClipped) {
10766
+ return vec2<f32>(intersection, 1.0);
10767
+ }
10768
+ return vec2<f32>(0.0, intersection);
10769
+ }
10770
+ return vec2<f32>(0.0, 1.0);
10771
+ }
10772
+ #endif
10773
+
10727
10774
  fn getLineJoinOffset(
10728
10775
  prevPoint: vec3<f32>,
10729
10776
  currPoint: vec3<f32>,
10730
10777
  nextPoint: vec3<f32>,
10731
10778
  width: vec2<f32>,
10779
+ #ifdef DASH_ENABLED
10780
+ sourcePathLength: f32,
10781
+ sourcePathRange: vec2<f32>,
10782
+ #endif
10732
10783
  #ifdef ANTIALIASING
10733
10784
  coverageScale: f32,
10734
10785
  #endif
@@ -10765,7 +10816,43 @@ fn getLineJoinOffset(
10765
10816
  let miterVec = vec2<f32>(-tangent.y, tangent.x);
10766
10817
  let dir = select(dirB, dirA, isEnd);
10767
10818
  let perp = select(perpB, perpA, isEnd);
10819
+ #ifdef DASH_ENABLED
10820
+ let segmentLength2D = select(lenB, lenA, isEnd);
10821
+
10822
+ // Extrusion happens in the XY plane, so segmentLength2D is a 2D length and pathPosition.y
10823
+ // below measures 2D distance along the segment. For a path that also moves in Z the true
10824
+ // arc length is longer by this ratio. Scaling pathLength and pathPosition.y by it makes
10825
+ // the coordinate measure real 3D distance while leaving the joint tests unchanged, since
10826
+ // they compare the two against each other and both are scaled alike. Billboard mode
10827
+ // extrudes in clip space, where the perspective divide has already reduced the segment to
10828
+ // its screen projection, so its complete common-space length is supplied by the caller.
10829
+ // Mirrors path-layer-vertex.glsl.ts.
10830
+ let currDelta3 = select(deltaB3, deltaA3, isEnd);
10831
+ let currLength2D = length(currDelta3.xy);
10832
+ // Do not clamp a valid denominator to EPSILON: high-zoom Web Mercator deltas are often
10833
+ // smaller than that in common space, and changing their scale corrupts even flat paths.
10834
+ let safeLength2D = select(1.0, currLength2D, currLength2D > 0.0);
10835
+ var arcLengthRatio = 1.0;
10836
+ var pathPositionOffset = 0.0;
10837
+ var pathLength = segmentLength2D;
10838
+ if (path.billboard != 0.0) {
10839
+ // clipLine may shorten the visible screen-space segment. Preserve the corresponding interval
10840
+ // of the complete common-space arclength instead of compressing the full dash period into the
10841
+ // visible span. Keep pathLength complete so justification is stable as the camera clips it.
10842
+ let visiblePathLength = sourcePathLength * (sourcePathRange.y - sourcePathRange.x);
10843
+ arcLengthRatio = 0.0;
10844
+ if (segmentLength2D > 0.0) {
10845
+ arcLengthRatio = visiblePathLength / segmentLength2D;
10846
+ }
10847
+ pathPositionOffset = sourcePathLength * sourcePathRange.x;
10848
+ pathLength = sourcePathLength;
10849
+ } else if (currLength2D > 0.0) {
10850
+ arcLengthRatio = length(currDelta3) / safeLength2D;
10851
+ pathLength = segmentLength2D * arcLengthRatio;
10852
+ }
10853
+ #else
10768
10854
  let pathLength = select(lenB, lenA, isEnd);
10855
+ #endif
10769
10856
 
10770
10857
  let sinHalfA = abs(dot(miterVec, perp));
10771
10858
  let cosHalfA = abs(dot(dirA, miterVec));
@@ -10812,7 +10899,11 @@ fn getLineJoinOffset(
10812
10899
  #endif
10813
10900
  let pathPosition = vec2<f32>(
10814
10901
  dot(offsetFromStartOfPath, perp),
10902
+ #ifdef DASH_ENABLED
10903
+ pathPositionOffset + dot(offsetFromStartOfPath, dir) * arcLengthRatio
10904
+ #else
10815
10905
  dot(offsetFromStartOfPath, dir)
10906
+ #endif
10816
10907
  );
10817
10908
  let isValid = step(f32(instanceTypes), 3.5);
10818
10909
  #ifdef ANTIALIASING
@@ -10871,15 +10962,38 @@ fn vertexMain(attributes: Attributes) -> Varyings {
10871
10962
  ) / 2.0;
10872
10963
 
10873
10964
  if (path.billboard != 0.0) {
10874
- var prevPositionScreen = project_position_to_clipspace(prevPosition, prevPosition64Low, ZERO_OFFSET);
10965
+ #ifdef DASH_ENABLED
10966
+ let prevProjection = project_position_to_clipspace_and_commonspace(
10967
+ prevPosition, prevPosition64Low, ZERO_OFFSET
10968
+ );
10969
+ let nextProjection = project_position_to_clipspace_and_commonspace(
10970
+ nextPosition, nextPosition64Low, ZERO_OFFSET
10971
+ );
10972
+ let prevPositionCommon = prevProjection.commonPosition.xyz;
10973
+ let nextPositionCommon = nextProjection.commonPosition.xyz;
10974
+ var prevPositionScreen = prevProjection.clipPosition;
10975
+ var nextPositionScreen = nextProjection.clipPosition;
10976
+ #else
10977
+ var prevPositionScreen = project_position_to_clipspace(
10978
+ prevPosition, prevPosition64Low, ZERO_OFFSET
10979
+ );
10980
+ var nextPositionScreen = project_position_to_clipspace(
10981
+ nextPosition, nextPosition64Low, ZERO_OFFSET
10982
+ );
10983
+ #endif
10875
10984
  let currProjection = project_position_to_clipspace_and_commonspace(
10876
- currPosition,
10877
- currPosition64Low,
10878
- ZERO_OFFSET
10985
+ currPosition, currPosition64Low, ZERO_OFFSET
10879
10986
  );
10880
10987
  geometry.position = currProjection.commonPosition;
10881
10988
  var currPositionScreen = currProjection.clipPosition;
10882
- var nextPositionScreen = project_position_to_clipspace(nextPosition, nextPosition64Low, ZERO_OFFSET);
10989
+ #ifdef DASH_ENABLED
10990
+ let currPositionCommon = currProjection.commonPosition.xyz;
10991
+ let sourcePathStartScreen = mix(currPositionScreen, prevPositionScreen, isEnd);
10992
+ let sourcePathEndScreen = mix(nextPositionScreen, currPositionScreen, isEnd);
10993
+ let billboardPathRange = getClippedPathRange(
10994
+ sourcePathStartScreen.w, sourcePathEndScreen.w
10995
+ );
10996
+ #endif
10883
10997
 
10884
10998
  prevPositionScreen = clipLine(prevPositionScreen, currPositionScreen);
10885
10999
  nextPositionScreen = clipLine(nextPositionScreen, currPositionScreen);
@@ -10891,18 +11005,39 @@ fn vertexMain(attributes: Attributes) -> Varyings {
10891
11005
  (widthPixels + 0.5 / project.devicePixelRatio) / max(widthPixels, 1e-6),
10892
11006
  widthPixels > 0.0
10893
11007
  );
11008
+ #endif
11009
+ #ifdef DASH_ENABLED
11010
+ let currentDeltaCommon = select(
11011
+ nextPositionCommon - currPositionCommon,
11012
+ currPositionCommon - prevPositionCommon,
11013
+ isEnd > 0.0
11014
+ );
11015
+ let billboardPathLength = select(
11016
+ 0.0,
11017
+ length(currentDeltaCommon) * project.scale / (widthPixels * project.focalDistance),
11018
+ widthPixels > 0.0
11019
+ );
10894
11020
  #endif
10895
11021
  let join = getLineJoinOffset(
10896
11022
  prevPositionScreen.xyz / prevPositionScreen.w,
10897
11023
  currPositionScreen.xyz / currPositionScreen.w,
10898
11024
  nextPositionScreen.xyz / nextPositionScreen.w,
10899
11025
  project_pixel_size_to_clipspace(vec2<f32>(widthPixels, widthPixels)),
11026
+ #ifdef DASH_ENABLED
11027
+ billboardPathLength,
11028
+ billboardPathRange,
11029
+ #endif
10900
11030
  #ifdef ANTIALIASING
10901
11031
  coverageScale,
10902
11032
  #endif
10903
11033
  attributes.positions,
10904
11034
  attributes.instanceTypes
10905
11035
  );
11036
+ #ifdef DASH_ENABLED
11037
+ // Phase and justification use the complete source segment, while cap and joint coverage
11038
+ // must still recognize the endpoints moved by clipLine.
11039
+ varyings.vPathBounds = billboardPathLength * billboardPathRange;
11040
+ #endif
10906
11041
 
10907
11042
  geometry.uv = join.pathPosition;
10908
11043
  varyings.position = vec4<f32>(
@@ -10935,12 +11070,19 @@ fn vertexMain(attributes: Attributes) -> Varyings {
10935
11070
  currPositionCommon,
10936
11071
  nextPositionCommon,
10937
11072
  width,
11073
+ #ifdef DASH_ENABLED
11074
+ 1.0,
11075
+ vec2<f32>(0.0, 1.0),
11076
+ #endif
10938
11077
  #ifdef ANTIALIASING
10939
11078
  coverageScale,
10940
11079
  #endif
10941
11080
  attributes.positions,
10942
11081
  attributes.instanceTypes
10943
11082
  );
11083
+ #ifdef DASH_ENABLED
11084
+ varyings.vPathBounds = vec2<f32>(0.0, join.pathLength);
11085
+ #endif
10944
11086
 
10945
11087
  geometry.position = vec4<f32>(currPositionCommon + join.offset, 1.0);
10946
11088
  geometry.uv = join.pathPosition;
@@ -10971,7 +11113,13 @@ fn fragmentMain(varyings: Varyings) -> @location(0) vec4<f32> {
10971
11113
  // bounded by the corner offset, everywhere else by the edge of the stroke. Dividing by the
10972
11114
  // screen-space derivative converts the distance to the boundary into device pixels, which stays
10973
11115
  // correct under perspective foreshortening and under extensions that rescale the stroke.
11116
+ #ifdef DASH_ENABLED
11117
+ let isCorner =
11118
+ varyings.vPathPosition.y < varyings.vPathBounds.x ||
11119
+ varyings.vPathPosition.y > varyings.vPathBounds.y;
11120
+ #else
10974
11121
  let isCorner = varyings.vPathPosition.y < 0.0 || varyings.vPathPosition.y > varyings.vPathLength;
11122
+ #endif
10975
11123
  let isRound = varyings.vJointType > 0.5;
10976
11124
 
10977
11125
  // Distance to the silhouette in device pixels, from the derivative of the coordinate that
@@ -10982,7 +11130,13 @@ fn fragmentMain(varyings: Varyings) -> @location(0) vec4<f32> {
10982
11130
  // Both evaluated so each derivative stays on one field across the corner/body boundary
10983
11131
  let bodyPixels = (1.0 - bodyCoord) / max(fwidth(bodyCoord), 1e-6);
10984
11132
  let cornerPixels = (1.0 - cornerCoord) / max(fwidth(cornerCoord), 1e-6);
11133
+ #ifdef PATH_STYLE_OFFSET
11134
+ // Rounded corners still intersect the stroke-width envelope. Extensions may remap
11135
+ // vPathPosition.x independently of vCornerOffset, as PathStyleExtension does for offsets.
11136
+ let edgePixels = select(bodyPixels, min(cornerPixels, bodyPixels), isRound && isCorner);
11137
+ #else
10985
11138
  let edgePixels = select(bodyPixels, cornerPixels, isRound && isCorner);
11139
+ #endif
10986
11140
 
10987
11141
  // Fragments outside the coverage ramp must not write depth or picking colors.
10988
11142
  if (edgePixels <= -SMOOTH_EDGE_RADIUS) {
@@ -11000,11 +11154,18 @@ fn fragmentMain(varyings: Varyings) -> @location(0) vec4<f32> {
11000
11154
  // Feather one device pixel across the width only, before premultiplication. edgePixels is a
11001
11155
  // signed device-pixel distance and SMOOTH_EDGE_RADIUS is 0.5, so this ramps across one pixel.
11002
11156
  color.a *= smoothedge(0.0, edgePixels);
11157
+ #else
11158
+ #ifdef DASH_ENABLED
11159
+ if (
11160
+ varyings.vPathPosition.y < varyings.vPathBounds.x ||
11161
+ varyings.vPathPosition.y > varyings.vPathBounds.y
11162
+ ) {
11003
11163
  #else
11004
11164
  if (
11005
11165
  varyings.vPathPosition.y < 0.0 ||
11006
11166
  varyings.vPathPosition.y > varyings.vPathLength
11007
11167
  ) {
11168
+ #endif
11008
11169
  if (varyings.vJointType > 0.5 && length(varyings.vCornerOffset) > 1.0) {
11009
11170
  discard;
11010
11171
  }
@@ -11057,6 +11218,9 @@ out float vMiterLength;
11057
11218
  out vec2 vPathPosition;
11058
11219
  out float vPathLength;
11059
11220
  out float vJointType;
11221
+ #ifdef DASH_ENABLED
11222
+ out vec2 vPathBounds;
11223
+ #endif
11060
11224
 
11061
11225
  const float EPSILON = 0.001;
11062
11226
  const vec3 ZERO_OFFSET = vec3(0.0);
@@ -11069,6 +11233,9 @@ float flipIfTrue(bool flag) {
11069
11233
  vec3 getLineJoinOffset(
11070
11234
  vec3 prevPoint, vec3 currPoint, vec3 nextPoint,
11071
11235
  vec2 width
11236
+ #ifdef DASH_ENABLED
11237
+ , float sourcePathLength, vec2 sourcePathRange
11238
+ #endif
11072
11239
  #ifdef ANTIALIASING
11073
11240
  , float coverageScale
11074
11241
  #endif
@@ -11111,6 +11278,37 @@ vec3 getLineJoinOffset(
11111
11278
  // length of the segment
11112
11279
  float L = isEnd ? lenA : lenB;
11113
11280
 
11281
+ #ifdef DASH_ENABLED
11282
+ // Extrusion happens in the XY plane, so L above is a 2D length and vPathPosition.y below
11283
+ // measures 2D distance along the segment. For a path that also moves in Z the true arc
11284
+ // length is longer by this ratio. Scaling vPathLength and vPathPosition.y by it makes the
11285
+ // coordinate measure real 3D distance - which is what CPU-side dash offsets accumulate -
11286
+ // while leaving the joint tests in the fragment shader unchanged, since they compare the
11287
+ // two against each other and both are scaled alike.
11288
+ // Billboard mode extrudes in clip space, where the perspective divide has already reduced
11289
+ // the segment to its screen projection, so its complete common-space length is supplied by
11290
+ // the caller.
11291
+ vec3 currDelta3 = isEnd ? deltaA3 : deltaB3;
11292
+ float currLength2D = length(currDelta3.xy);
11293
+ float arcLengthRatio = 1.0;
11294
+ float pathPositionOffset = 0.0;
11295
+ float pathLength = L;
11296
+ if (path.billboard) {
11297
+ // clipLine may shorten the visible screen-space segment. Preserve the corresponding interval
11298
+ // of the complete common-space arclength instead of compressing the full dash period into the
11299
+ // visible span. Keep pathLength complete so justification is stable as the camera clips it.
11300
+ float visiblePathLength = sourcePathLength * (sourcePathRange.y - sourcePathRange.x);
11301
+ arcLengthRatio = L > 0.0 ? visiblePathLength / L : 0.0;
11302
+ pathPositionOffset = sourcePathLength * sourcePathRange.x;
11303
+ pathLength = sourcePathLength;
11304
+ } else if (currLength2D > 0.0) {
11305
+ // Do not clamp a valid denominator to EPSILON: high-zoom Web Mercator deltas are often
11306
+ // smaller than that in common space, and changing their scale corrupts even flat paths.
11307
+ arcLengthRatio = length(currDelta3) / currLength2D;
11308
+ pathLength = L * arcLengthRatio;
11309
+ }
11310
+ #endif
11311
+
11114
11312
  // A = angle of the corner
11115
11313
  float sinHalfA = abs(dot(miterVec, perp));
11116
11314
  float cosHalfA = abs(dot(dirA, miterVec));
@@ -11152,7 +11350,11 @@ vec3 getLineJoinOffset(
11152
11350
  // The physical stroke still ends at offsetVec; the scaled coordinates and vertices only extend
11153
11351
  // its rasterized envelope to include the outer half of the centered coverage ramp.
11154
11352
  vec2 coverageOffsetVec = offsetVec * coverageScale;
11353
+ #ifdef DASH_ENABLED
11354
+ vPathLength = pathLength;
11355
+ #else
11155
11356
  vPathLength = L;
11357
+ #endif
11156
11358
  vCornerOffset = coverageOffsetVec;
11157
11359
  vMiterLength = dot(vCornerOffset, miterVec * turnDirection);
11158
11360
  vMiterLength = isCap ? isJoint : vMiterLength;
@@ -11160,7 +11362,11 @@ vec3 getLineJoinOffset(
11160
11362
  vec2 offsetFromStartOfPath = coverageOffsetVec + deltaA * float(isEnd);
11161
11363
  vPathPosition = vec2(
11162
11364
  dot(offsetFromStartOfPath, perp),
11365
+ #ifdef DASH_ENABLED
11366
+ pathPositionOffset + dot(offsetFromStartOfPath, dir) * arcLengthRatio
11367
+ #else
11163
11368
  dot(offsetFromStartOfPath, dir)
11369
+ #endif
11164
11370
  );
11165
11371
  geometry.uv = vPathPosition;
11166
11372
 
@@ -11168,7 +11374,11 @@ vec3 getLineJoinOffset(
11168
11374
  vec3 offset = vec3(coverageOffsetVec * width * isValid, 0.0);
11169
11375
  #else
11170
11376
  // Generate variables for fragment shader
11377
+ #ifdef DASH_ENABLED
11378
+ vPathLength = pathLength;
11379
+ #else
11171
11380
  vPathLength = L;
11381
+ #endif
11172
11382
  vCornerOffset = offsetVec;
11173
11383
  vMiterLength = dot(vCornerOffset, miterVec * turnDirection);
11174
11384
  vMiterLength = isCap ? isJoint : vMiterLength;
@@ -11176,7 +11386,11 @@ vec3 getLineJoinOffset(
11176
11386
  vec2 offsetFromStartOfPath = vCornerOffset + deltaA * float(isEnd);
11177
11387
  vPathPosition = vec2(
11178
11388
  dot(offsetFromStartOfPath, perp),
11389
+ #ifdef DASH_ENABLED
11390
+ pathPositionOffset + dot(offsetFromStartOfPath, dir) * arcLengthRatio
11391
+ #else
11179
11392
  dot(offsetFromStartOfPath, dir)
11393
+ #endif
11180
11394
  );
11181
11395
  geometry.uv = vPathPosition;
11182
11396
 
@@ -11198,6 +11412,22 @@ void clipLine(inout vec4 position, vec4 refPosition) {
11198
11412
  }
11199
11413
  }
11200
11414
 
11415
+ #ifdef DASH_ENABLED
11416
+ // Return the visible interval of the original segment before clipLine moves either endpoint.
11417
+ vec2 getClippedPathRange(float startW, float endW) {
11418
+ bool startClipped = startW < EPSILON;
11419
+ bool endClipped = endW < EPSILON;
11420
+ if (startClipped && endClipped) {
11421
+ return vec2(0.0);
11422
+ }
11423
+ if (startClipped || endClipped) {
11424
+ float intersection = clamp((EPSILON - startW) / (endW - startW), 0.0, 1.0);
11425
+ return startClipped ? vec2(intersection, 1.0) : vec2(0.0, intersection);
11426
+ }
11427
+ return vec2(0.0, 1.0);
11428
+ }
11429
+ #endif
11430
+
11201
11431
  void main() {
11202
11432
  geometry.pickingColor = picking_getPickingColorFromIndex(rowIndexes);
11203
11433
 
@@ -11222,9 +11452,35 @@ void main() {
11222
11452
 
11223
11453
  if (path.billboard) {
11224
11454
  // Extrude in clipspace
11225
- vec4 prevPositionScreen = project_position_to_clipspace(prevPosition, prevPosition64Low, ZERO_OFFSET);
11455
+ #ifdef DASH_ENABLED
11456
+ vec4 prevPositionCommon;
11457
+ vec4 nextPositionCommon;
11458
+ vec4 prevPositionScreen = project_position_to_clipspace(
11459
+ prevPosition, prevPosition64Low, ZERO_OFFSET, prevPositionCommon
11460
+ );
11461
+ #else
11462
+ vec4 prevPositionScreen = project_position_to_clipspace(
11463
+ prevPosition, prevPosition64Low, ZERO_OFFSET
11464
+ );
11465
+ #endif
11226
11466
  vec4 currPositionScreen = project_position_to_clipspace(currPosition, currPosition64Low, ZERO_OFFSET, geometry.position);
11227
- vec4 nextPositionScreen = project_position_to_clipspace(nextPosition, nextPosition64Low, ZERO_OFFSET);
11467
+ #ifdef DASH_ENABLED
11468
+ vec4 nextPositionScreen = project_position_to_clipspace(
11469
+ nextPosition, nextPosition64Low, ZERO_OFFSET, nextPositionCommon
11470
+ );
11471
+ #else
11472
+ vec4 nextPositionScreen = project_position_to_clipspace(
11473
+ nextPosition, nextPosition64Low, ZERO_OFFSET
11474
+ );
11475
+ #endif
11476
+
11477
+ #ifdef DASH_ENABLED
11478
+ vec4 sourcePathStartScreen = mix(currPositionScreen, prevPositionScreen, isEnd);
11479
+ vec4 sourcePathEndScreen = mix(nextPositionScreen, currPositionScreen, isEnd);
11480
+ vec2 billboardPathRange = getClippedPathRange(
11481
+ sourcePathStartScreen.w, sourcePathEndScreen.w
11482
+ );
11483
+ #endif
11228
11484
 
11229
11485
  clipLine(prevPositionScreen, currPositionScreen);
11230
11486
  clipLine(nextPositionScreen, currPositionScreen);
@@ -11239,16 +11495,34 @@ void main() {
11239
11495
  : 1.0;
11240
11496
  #endif
11241
11497
 
11498
+ #ifdef DASH_ENABLED
11499
+ vec3 currentDeltaCommon = isEnd > 0.0
11500
+ ? geometry.position.xyz - prevPositionCommon.xyz
11501
+ : nextPositionCommon.xyz - geometry.position.xyz;
11502
+ float billboardPathLength = width.x > 0.0
11503
+ ? length(currentDeltaCommon) * project.scale / (width.x * project.focalDistance)
11504
+ : 0.0;
11505
+ #endif
11506
+
11242
11507
  vec3 offset = getLineJoinOffset(
11243
11508
  prevPositionScreen.xyz / prevPositionScreen.w,
11244
11509
  currPositionScreen.xyz / currPositionScreen.w,
11245
11510
  nextPositionScreen.xyz / nextPositionScreen.w,
11246
11511
  project_pixel_size_to_clipspace(width.xy)
11512
+ #ifdef DASH_ENABLED
11513
+ ,
11514
+ billboardPathLength, billboardPathRange
11515
+ #endif
11247
11516
  #ifdef ANTIALIASING
11248
11517
  ,
11249
11518
  coverageScale
11250
11519
  #endif
11251
11520
  );
11521
+ #ifdef DASH_ENABLED
11522
+ // Phase and justification use the complete source segment, while cap and joint coverage
11523
+ // must still recognize the endpoints moved by clipLine.
11524
+ vPathBounds = billboardPathLength * billboardPathRange;
11525
+ #endif
11252
11526
 
11253
11527
  DECKGL_FILTER_GL_POSITION(currPositionScreen, geometry);
11254
11528
  gl_Position = vec4(currPositionScreen.xyz + offset * currPositionScreen.w, currPositionScreen.w);
@@ -11269,10 +11543,16 @@ void main() {
11269
11543
 
11270
11544
  vec3 offset = getLineJoinOffset(
11271
11545
  prevPosition, currPosition, nextPosition, width.xy
11546
+ #ifdef DASH_ENABLED
11547
+ , 1.0, vec2(0.0, 1.0)
11548
+ #endif
11272
11549
  #ifdef ANTIALIASING
11273
11550
  , coverageScale
11274
11551
  #endif
11275
11552
  );
11553
+ #ifdef DASH_ENABLED
11554
+ vPathBounds = vec2(0.0, vPathLength);
11555
+ #endif
11276
11556
  geometry.position = vec4(currPosition + offset, 1.0);
11277
11557
  gl_Position = project_common_position_to_clipspace(geometry.position);
11278
11558
  DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
@@ -11298,6 +11578,9 @@ in float vMiterLength;
11298
11578
  in vec2 vPathPosition;
11299
11579
  in float vPathLength;
11300
11580
  in float vJointType;
11581
+ #ifdef DASH_ENABLED
11582
+ in vec2 vPathBounds;
11583
+ #endif
11301
11584
 
11302
11585
  out vec4 fragColor;
11303
11586
 
@@ -11305,7 +11588,11 @@ void main(void) {
11305
11588
  geometry.uv = vPathPosition;
11306
11589
 
11307
11590
  #ifdef ANTIALIASING
11591
+ #ifdef DASH_ENABLED
11592
+ bool isCorner = vPathPosition.y < vPathBounds.x || vPathPosition.y > vPathBounds.y;
11593
+ #else
11308
11594
  bool isCorner = vPathPosition.y < 0.0 || vPathPosition.y > vPathLength;
11595
+ #endif
11309
11596
  bool isRound = vJointType > 0.5;
11310
11597
 
11311
11598
  // Distance to the silhouette in device pixels, from the derivative of the coordinate that
@@ -11316,7 +11603,13 @@ void main(void) {
11316
11603
  // Both evaluated so each derivative stays on one field across the corner/body boundary
11317
11604
  float bodyPixels = (1.0 - bodyCoord) / max(fwidth(bodyCoord), 1e-6);
11318
11605
  float cornerPixels = (1.0 - cornerCoord) / max(fwidth(cornerCoord), 1e-6);
11606
+ #ifdef PATH_STYLE_OFFSET
11607
+ // Rounded corners still intersect the stroke-width envelope. Extensions may remap
11608
+ // vPathPosition.x independently of vCornerOffset, as PathStyleExtension does for offsets.
11609
+ float edgePixels = isRound && isCorner ? min(cornerPixels, bodyPixels) : bodyPixels;
11610
+ #else
11319
11611
  float edgePixels = isRound && isCorner ? cornerPixels : bodyPixels;
11612
+ #endif
11320
11613
 
11321
11614
  // Fragments outside the coverage ramp must not write depth or picking colors.
11322
11615
  if (edgePixels <= -SMOOTH_EDGE_RADIUS) {
@@ -11335,8 +11628,12 @@ void main(void) {
11335
11628
  // edgePixels is a signed device-pixel distance, and SMOOTH_EDGE_RADIUS is 0.5, so smoothedge
11336
11629
  // ramps across exactly one pixel centered on the edge.
11337
11630
  fragColor.a *= smoothedge(0.0, edgePixels);
11631
+ #else
11632
+ #ifdef DASH_ENABLED
11633
+ if (vPathPosition.y < vPathBounds.x || vPathPosition.y > vPathBounds.y) {
11338
11634
  #else
11339
11635
  if (vPathPosition.y < 0.0 || vPathPosition.y > vPathLength) {
11636
+ #endif
11340
11637
  // if joint is rounded, test distance from the corner
11341
11638
  if (vJointType > 0.5 && length(vCornerOffset) > 1.0) {
11342
11639
  discard;
@@ -11377,6 +11674,18 @@ var ATTRIBUTE_TRANSITION = {
11377
11674
  return chunk.length ? chunk.subarray(chunk.length - value.length) : value;
11378
11675
  }
11379
11676
  };
11677
+ function getPathProjectionScale(viewport) {
11678
+ if (viewport.isGeospatial) {
11679
+ return null;
11680
+ }
11681
+ const { unitsPerMeter: unitsPerMeter2 } = viewport.distanceScales;
11682
+ return [unitsPerMeter2[0], unitsPerMeter2[1], unitsPerMeter2[2]];
11683
+ }
11684
+ function pathProjectionScalesEqual(left, right) {
11685
+ return left === right || Boolean(
11686
+ left && right && left.length === right.length && left.every((value, index) => value === right[index])
11687
+ );
11688
+ }
11380
11689
  var PathLayer = class extends Layer {
11381
11690
  getShaders() {
11382
11691
  const { antialiasing } = this.props;
@@ -11404,6 +11713,43 @@ var PathLayer = class extends Layer {
11404
11713
  }
11405
11714
  return (_a = this.getAttributeManager()) == null ? void 0 : _a.getBounds(["vertexPositions"]);
11406
11715
  }
11716
+ getPathProjectionScale(viewport) {
11717
+ var _a;
11718
+ const coordinateSystem = this.props.coordinateSystem;
11719
+ const hasDashMetrics = Boolean((_a = this.getAttributeManager()) == null ? void 0 : _a.getAttributes().instanceDashOffsets);
11720
+ if (!hasDashMetrics) {
11721
+ return null;
11722
+ }
11723
+ const trackViewportScale = viewport instanceof WebMercatorViewport && viewport.zoom >= 12 && // Offset coordinate systems use their fixed coordinateOrigin as the projection origin.
11724
+ // Default/lnglat and preprojected Cartesian paths follow the viewport center instead.
11725
+ (coordinateSystem === "default" || coordinateSystem === "lnglat" || coordinateSystem === "cartesian");
11726
+ if (trackViewportScale) {
11727
+ const projectUniforms = project_default.getUniforms({
11728
+ viewport,
11729
+ coordinateSystem,
11730
+ coordinateOrigin: this.props.coordinateOrigin,
11731
+ autoWrapLongitude: this.wrapLongitude
11732
+ });
11733
+ return [
11734
+ viewport.projectionMode,
11735
+ projectUniforms.coordinateOrigin[1],
11736
+ projectUniforms.commonOrigin[1],
11737
+ ...projectUniforms.commonUnitsPerWorldUnit,
11738
+ ...projectUniforms.commonUnitsPerWorldUnit2,
11739
+ projectUniforms.commonUnitsPerMeter[2]
11740
+ ];
11741
+ }
11742
+ const projectionScale = getPathProjectionScale(viewport);
11743
+ return projectionScale ? [viewport.projectionMode, ...projectionScale] : [viewport.projectionMode];
11744
+ }
11745
+ shouldUpdateState(params) {
11746
+ var _a, _b;
11747
+ const { viewport } = this.context;
11748
+ return super.shouldUpdateState(params) || ((_a = this.state) == null ? void 0 : _a.tessellationResolution) !== viewport.resolution || !pathProjectionScalesEqual(
11749
+ (_b = this.state) == null ? void 0 : _b.pathProjectionScale,
11750
+ this.getPathProjectionScale(viewport)
11751
+ );
11752
+ }
11407
11753
  initializeState() {
11408
11754
  const noAlloc = true;
11409
11755
  const isWebGPU = this.context.device.type === "webgpu";
@@ -11494,7 +11840,9 @@ var PathLayer = class extends Layer {
11494
11840
  pathTesselator: new PathTesselator({
11495
11841
  fp64: this.use64bitPositions(),
11496
11842
  isWebGPU
11497
- })
11843
+ }),
11844
+ tessellationResolution: this.context.viewport.resolution,
11845
+ pathProjectionScale: this.getPathProjectionScale(this.context.viewport)
11498
11846
  });
11499
11847
  }
11500
11848
  updateState(params) {
@@ -11502,7 +11850,16 @@ var PathLayer = class extends Layer {
11502
11850
  super.updateState(params);
11503
11851
  const { props, oldProps, changeFlags } = params;
11504
11852
  const attributeManager = this.getAttributeManager();
11505
- const geometryChanged = changeFlags.dataChanged || changeFlags.updateTriggersChanged && (changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getPath);
11853
+ const { viewport } = this.context;
11854
+ const tessellationResolutionChanged = this.state.tessellationResolution !== viewport.resolution;
11855
+ const pathProjectionScale = this.getPathProjectionScale(viewport);
11856
+ const pathProjectionScaleChanged = !pathProjectionScalesEqual(
11857
+ this.state.pathProjectionScale,
11858
+ pathProjectionScale
11859
+ );
11860
+ const getPathChanged = changeFlags.updateTriggersChanged && (changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getPath);
11861
+ const geometryConfigurationChanged = getPathChanged || props._pathType !== oldProps._pathType || props.positionFormat !== oldProps.positionFormat || props.wrapLongitude !== oldProps.wrapLongitude || tessellationResolutionChanged;
11862
+ const geometryChanged = changeFlags.dataChanged || geometryConfigurationChanged;
11506
11863
  if (geometryChanged) {
11507
11864
  const { pathTesselator } = this.state;
11508
11865
  const buffers = props.data.attributes || {};
@@ -11516,16 +11873,24 @@ var PathLayer = class extends Layer {
11516
11873
  positionFormat: props.positionFormat,
11517
11874
  wrapLongitude: props.wrapLongitude,
11518
11875
  // TODO - move the flag out of the viewport
11519
- resolution: this.context.viewport.resolution,
11520
- dataChanged: changeFlags.dataChanged
11876
+ resolution: viewport.resolution,
11877
+ // A partial data diff is only valid while normalization inputs remain unchanged.
11878
+ dataChanged: geometryConfigurationChanged ? void 0 : changeFlags.dataChanged
11521
11879
  });
11522
11880
  this.setState({
11523
11881
  numInstances: pathTesselator.instanceCount,
11524
- startIndices: pathTesselator.vertexStarts
11882
+ startIndices: pathTesselator.vertexStarts,
11883
+ tessellationResolution: viewport.resolution,
11884
+ pathProjectionScale
11525
11885
  });
11526
- if (!changeFlags.dataChanged) {
11886
+ if (!changeFlags.dataChanged || geometryConfigurationChanged) {
11527
11887
  attributeManager.invalidateAll();
11888
+ } else if (pathProjectionScaleChanged) {
11889
+ attributeManager.invalidate("instanceDashOffsets");
11528
11890
  }
11891
+ } else if (pathProjectionScaleChanged) {
11892
+ this.setState({ pathProjectionScale });
11893
+ attributeManager.invalidate("instanceDashOffsets");
11529
11894
  }
11530
11895
  if (changeFlags.extensionsChanged || props.antialiasing !== oldProps.antialiasing) {
11531
11896
  (_a = this.state.model) == null ? void 0 : _a.destroy();