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

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 +886 -2702
  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 +7 -7
  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
@@ -2,8 +2,14 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {LayerExtension, _mergeShaders as mergeShaders} from '@deck.gl/core';
6
- import {vec3} from '@math.gl/core';
5
+ import {
6
+ LayerExtension,
7
+ WebMercatorViewport,
8
+ project,
9
+ _deepEqual as deepEqual,
10
+ _mergeShaders as mergeShaders
11
+ } from '@deck.gl/core';
12
+ import {vec3, vec4} from '@math.gl/core';
7
13
  import {
8
14
  dashShaders,
9
15
  Defines,
@@ -12,19 +18,46 @@ import {
12
18
  textBackgroundDashShaders
13
19
  } from './shaders.glsl';
14
20
 
15
- import type {Accessor, Layer, LayerContext, UpdateParameters} from '@deck.gl/core';
21
+ import type {
22
+ Accessor,
23
+ Attribute,
24
+ Layer,
25
+ LayerContext,
26
+ ProjectUniforms,
27
+ UpdateParameters
28
+ } from '@deck.gl/core';
16
29
  import type {ShaderModule} from '@luma.gl/shadertools';
17
30
 
18
31
  const defaultProps = {
19
32
  getDashArray: {type: 'accessor', value: [0, 0]},
20
33
  getOffset: {type: 'accessor', value: 0},
21
34
  dashJustified: false,
22
- dashGapPickable: false
35
+ dashGapPickable: false,
36
+ dashUnits: 'widths'
37
+ };
38
+
39
+ /**
40
+ * What `getDashArray` is measured in.
41
+ *
42
+ * `'widths'` is relative to half the stroke width and is the historical behavior, so a dash
43
+ * scales with the line. `'pixels'` uses nominal zoom-stable projected pixels: exact for flat or
44
+ * orthographic paths and approximate under pitch, perspective, or elevation. `'meters'` uses the
45
+ * local projection scale, while `'common'` uses deck.gl common-coordinate-space units.
46
+ */
47
+ export type DashUnits = 'widths' | 'pixels' | 'meters' | 'common';
48
+
49
+ /** Keep in sync with the branch in the dash vertex shader. */
50
+ const DASH_UNITS: Record<DashUnits, number> = {
51
+ widths: 0,
52
+ pixels: 1,
53
+ meters: 2,
54
+ common: 3
23
55
  };
24
56
 
25
57
  type PathStyleProps = {
26
58
  dashAlignMode: number;
27
59
  dashGapPickable: boolean;
60
+ dashUnits: number;
28
61
  };
29
62
 
30
63
  type SDFDashStyleProps = {
@@ -33,7 +66,9 @@ type SDFDashStyleProps = {
33
66
 
34
67
  export type PathStyleExtensionProps<DataT = any> = {
35
68
  /**
36
- * Accessor for the dash array to draw each path with: `[dashSize, gapSize]` relative to the width of the path.
69
+ * Accessor for the dash array to draw each path with: `[dashSize, gapSize]` in the units
70
+ * selected by `dashUnits`. By default those units are relative to *half* the path width,
71
+ * so `[4, 5]` on a path 10 pixels wide draws 20 pixel dashes separated by 25 pixel gaps.
37
72
  * Requires the `dash` option to be on.
38
73
  */
39
74
  getDashArray?: Accessor<DataT, Readonly<[number, number]>>;
@@ -53,39 +88,134 @@ export type PathStyleExtensionProps<DataT = any> = {
53
88
  * @default false
54
89
  */
55
90
  dashGapPickable?: boolean;
91
+ /**
92
+ * What `getDashArray` is measured in. `'widths'` is relative to half the stroke width, so a
93
+ * dash scales with the line. `'pixels'` uses nominal zoom-stable projected pixels, which are
94
+ * exact for flat or orthographic paths and approximate under pitch, perspective, or elevation.
95
+ * `'meters'` uses the local projection scale and `'common'` uses deck.gl common space.
96
+ * Only applies to `PathLayer` and composite layers that render paths; scatterplot outlines
97
+ * and text backgrounds continue to interpret dash arrays relative to their stroke width.
98
+ * @default 'widths'
99
+ */
100
+ dashUnits?: DashUnits;
56
101
  };
57
102
 
103
+ /** How the dash pattern is positioned along a path. */
104
+ export type DashMode = 'segment' | 'path';
105
+
106
+ /** Options for configuring a {@link PathStyleExtension}. */
58
107
  export type PathStyleExtensionOptions = {
59
108
  /**
60
109
  * Add capability to render dashed lines.
61
110
  * @default false
62
111
  */
63
- dash: boolean;
112
+ dash?: boolean;
64
113
  /**
65
114
  * Add capability to offset lines.
66
115
  * @default false
67
116
  */
68
- offset: boolean;
117
+ offset?: boolean;
118
+ /**
119
+ * How the dash pattern is positioned along a path. `'path'` keeps dashes continuous across
120
+ * rendered segments, at the cost of a vertex attribute and a CPU pass over the geometry.
121
+ * @default 'segment'
122
+ */
123
+ dashMode?: DashMode;
69
124
  /**
70
125
  * Improve dash rendering quality in certain circumstances. Note that this option introduces additional performance overhead.
126
+ * @deprecated Use `dashMode: 'path'` instead.
71
127
  * @default false
72
128
  */
73
- highPrecisionDash: boolean;
129
+ highPrecisionDash?: boolean;
74
130
  };
75
131
 
132
+ type ResolvedPathStyleExtensionOptions = Required<PathStyleExtensionOptions>;
133
+
76
134
  type LayerType = 'path' | 'scatterplot' | 'textBackground';
77
135
 
136
+ const PATH_STYLE_ATTRIBUTES = ['instanceDashArrays', 'instanceDashOffsets', 'instanceOffsets'];
137
+
138
+ function dashMetricsProjectionPropsChanged({
139
+ props,
140
+ oldProps
141
+ }: UpdateParameters<Layer<PathStyleExtensionProps>>): boolean {
142
+ // Data, accessors, and tessellation configuration invalidate through AttributeManager and
143
+ // PathLayer. PathLayer separately tracks viewport projection scale. These are the remaining
144
+ // layer props consumed by the project uniforms used to calculate dash metrics.
145
+ return (
146
+ !deepEqual(props.modelMatrix, oldProps.modelMatrix, 2) ||
147
+ props.coordinateSystem !== oldProps.coordinateSystem ||
148
+ !deepEqual(props.coordinateOrigin, oldProps.coordinateOrigin, 1)
149
+ );
150
+ }
151
+
152
+ function projectRenderedPathPosition(
153
+ layer: Layer<PathStyleExtensionProps>,
154
+ position: number[],
155
+ projectUniforms: ProjectUniforms
156
+ ): [number, number, number] {
157
+ const viewport = layer.context.viewport;
158
+ if (!(viewport instanceof WebMercatorViewport) || viewport.zoom < 12) {
159
+ return layer.projectPosition(position, {autoOffset: false});
160
+ }
161
+
162
+ // High-zoom Web Mercator shaders use a center-relative Taylor projection for precision.
163
+ // Layer#projectPosition deliberately returns the exact, viewport-independent projection,
164
+ // so reproduce project_position here to keep CPU phase continuous with each GPU segment.
165
+ const positionWorld = vec4.transformMat4(
166
+ [],
167
+ [position[0], position[1], position[2] || 0, 1],
168
+ projectUniforms.modelMatrix
169
+ );
170
+ const coordinateSystem =
171
+ layer.props.coordinateSystem === 'default' ? 'lnglat' : layer.props.coordinateSystem;
172
+
173
+ if (coordinateSystem === 'lnglat') {
174
+ if (Math.abs(positionWorld[1] - projectUniforms.coordinateOrigin[1]) > 0.25) {
175
+ let longitude = positionWorld[0];
176
+ if (projectUniforms.wrapLongitude) {
177
+ longitude = ((((longitude + 180) % 360) + 360) % 360) - 180;
178
+ }
179
+ const commonPosition = viewport.projectPosition([longitude, positionWorld[1], 0]);
180
+ return [
181
+ commonPosition[0] - projectUniforms.commonOrigin[0],
182
+ commonPosition[1] - projectUniforms.commonOrigin[1],
183
+ positionWorld[2] * projectUniforms.commonUnitsPerMeter[2]
184
+ ];
185
+ }
186
+ vec3.sub(positionWorld, positionWorld, projectUniforms.coordinateOrigin);
187
+ } else if (coordinateSystem === 'cartesian') {
188
+ vec3.sub(positionWorld, positionWorld, projectUniforms.coordinateOrigin);
189
+ }
190
+
191
+ const deltaLatitude = positionWorld[1];
192
+ const scale = vec3.scaleAndAdd(
193
+ [],
194
+ projectUniforms.commonUnitsPerWorldUnit,
195
+ projectUniforms.commonUnitsPerWorldUnit2,
196
+ deltaLatitude
197
+ );
198
+ return vec3.multiply([], positionWorld, scale) as [number, number, number];
199
+ }
200
+
78
201
  /** Adds selected features to the `PathLayer`, `ScatterplotLayer`, `TextBackgroundLayer`, and composite layers that render them. */
79
- export default class PathStyleExtension extends LayerExtension<PathStyleExtensionOptions> {
202
+ export default class PathStyleExtension extends LayerExtension<ResolvedPathStyleExtensionOptions> {
80
203
  static defaultProps = defaultProps;
81
204
  static extensionName = 'PathStyleExtension';
82
205
 
83
206
  constructor({
84
207
  dash = false,
85
208
  offset = false,
209
+ dashMode,
86
210
  highPrecisionDash = false
87
- }: Partial<PathStyleExtensionOptions> = {}) {
88
- super({dash: dash || highPrecisionDash, offset, highPrecisionDash});
211
+ }: PathStyleExtensionOptions = {}) {
212
+ const resolvedDashMode: DashMode = dashMode ?? (highPrecisionDash ? 'path' : 'segment');
213
+ super({
214
+ dash: dash || highPrecisionDash || dashMode !== undefined,
215
+ offset,
216
+ dashMode: resolvedDashMode,
217
+ highPrecisionDash: resolvedDashMode === 'path'
218
+ });
89
219
  }
90
220
 
91
221
  private getLayerType(layer: Layer): LayerType | null {
@@ -102,6 +232,64 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
102
232
  return null;
103
233
  }
104
234
 
235
+ private synchronizeAttributes(layer: Layer<PathStyleExtensionProps>): boolean {
236
+ const attributeManager = layer.getAttributeManager();
237
+ const layerType = this.getLayerType(layer);
238
+ if (!attributeManager || !layerType) {
239
+ return false;
240
+ }
241
+
242
+ const attributes = attributeManager.getAttributes();
243
+ const desiredAttributes = new Set<string>();
244
+ if (this.opts.dash) {
245
+ desiredAttributes.add('instanceDashArrays');
246
+ }
247
+ if (layerType === 'path' && this.opts.dash && this.opts.dashMode === 'path') {
248
+ desiredAttributes.add('instanceDashOffsets');
249
+ }
250
+ if (layerType === 'path' && this.opts.offset) {
251
+ desiredAttributes.add('instanceOffsets');
252
+ }
253
+
254
+ let topologyChanged = false;
255
+ for (const attributeName of PATH_STYLE_ATTRIBUTES) {
256
+ if (attributes[attributeName] && !desiredAttributes.has(attributeName)) {
257
+ attributeManager.remove([attributeName]);
258
+ topologyChanged = true;
259
+ }
260
+ }
261
+
262
+ if (desiredAttributes.has('instanceDashArrays') && !attributes.instanceDashArrays) {
263
+ attributeManager.addInstanced({
264
+ instanceDashArrays: {size: 2, accessor: 'getDashArray'}
265
+ });
266
+ topologyChanged = true;
267
+ }
268
+ if (desiredAttributes.has('instanceDashOffsets') && !attributes.instanceDashOffsets) {
269
+ attributeManager.addInstanced({
270
+ instanceDashOffsets: {
271
+ // [distance from the start of the path, total length of the path]
272
+ size: 2,
273
+ // Recalculate from rendered geometry when getPath changes. A binary getPath buffer
274
+ // contains positions, not dash metrics, so keep it as an update trigger rather than
275
+ // binding it directly to this attribute.
276
+ accessor: ['getPath'],
277
+ // eslint-disable-next-line @typescript-eslint/unbound-method
278
+ update: this.calculateDashMetrics
279
+ }
280
+ });
281
+ topologyChanged = true;
282
+ }
283
+ if (desiredAttributes.has('instanceOffsets') && !attributes.instanceOffsets) {
284
+ attributeManager.addInstanced({
285
+ instanceOffsets: {size: 1, accessor: 'getOffset'}
286
+ });
287
+ topologyChanged = true;
288
+ }
289
+
290
+ return topologyChanged;
291
+ }
292
+
105
293
  isEnabled(layer: Layer<PathStyleExtensionProps>): boolean {
106
294
  return this.getLayerType(layer) !== null;
107
295
  }
@@ -135,23 +323,28 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
135
323
  const defines: Defines = {};
136
324
  if (extension.opts.dash) {
137
325
  result = mergeShaders(result, dashShaders);
138
- if (extension.opts.highPrecisionDash) {
326
+ defines.DASH_ENABLED = true;
327
+ if (extension.opts.dashMode === 'path') {
139
328
  defines.HIGH_PRECISION_DASH = true;
140
329
  }
141
330
  }
142
331
  if (extension.opts.offset) {
143
332
  result = mergeShaders(result, offsetShaders);
333
+ defines.PATH_STYLE_OFFSET = true;
144
334
  }
145
335
 
146
336
  const {inject} = result;
147
337
  const pathStyle: ShaderModule<PathStyleProps> = {
148
338
  name: 'pathStyle',
149
- inject,
150
- uniformTypes: {
151
- dashAlignMode: 'f32',
152
- dashGapPickable: 'i32'
153
- }
339
+ inject
154
340
  };
341
+ if (extension.opts.dash) {
342
+ pathStyle.uniformTypes = {
343
+ dashAlignMode: 'f32',
344
+ dashGapPickable: 'i32',
345
+ dashUnits: 'i32'
346
+ };
347
+ }
155
348
  return {
156
349
  modules: [pathStyle],
157
350
  defines
@@ -159,31 +352,7 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
159
352
  }
160
353
 
161
354
  initializeState(this: Layer<PathStyleExtensionProps>, context: LayerContext, extension: this) {
162
- const attributeManager = this.getAttributeManager();
163
- const layerType = extension.getLayerType(this);
164
- if (!attributeManager || !layerType) {
165
- return;
166
- }
167
-
168
- if (extension.opts.dash) {
169
- attributeManager.addInstanced({
170
- instanceDashArrays: {size: 2, accessor: 'getDashArray'},
171
- ...(layerType === 'path' && extension.opts.highPrecisionDash
172
- ? {
173
- instanceDashOffsets: {
174
- size: 1,
175
- accessor: 'getPath',
176
- transform: extension.getDashOffsets.bind(this)
177
- }
178
- }
179
- : {})
180
- });
181
- }
182
- if (layerType === 'path' && extension.opts.offset) {
183
- attributeManager.addInstanced({
184
- instanceOffsets: {size: 1, accessor: 'getOffset'}
185
- });
186
- }
355
+ extension.synchronizeAttributes(this);
187
356
  }
188
357
 
189
358
  updateState(
@@ -195,8 +364,29 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
195
364
  return;
196
365
  }
197
366
 
367
+ if (params.changeFlags.extensionsChanged) {
368
+ const topologyChanged = extension.synchronizeAttributes(this);
369
+ const attributeManager = this.getAttributeManager();
370
+ if (attributeManager && topologyChanged) {
371
+ // PathLayer recreates its model before extension updateState runs. Refresh the new
372
+ // model's layout after adding or removing extension-owned attributes, then ensure all
373
+ // managed buffers are rebound to the recreated vertex array.
374
+ attributeManager.invalidateAll();
375
+ for (const model of this.getModels()) {
376
+ model.setBufferLayout(attributeManager.getBufferLayouts(model));
377
+ }
378
+ }
379
+ }
380
+
198
381
  if (extension.opts.dash) {
199
382
  const layerType = extension.getLayerType(this);
383
+ if (
384
+ layerType === 'path' &&
385
+ extension.opts.dashMode === 'path' &&
386
+ dashMetricsProjectionPropsChanged(params)
387
+ ) {
388
+ this.getAttributeManager()?.invalidate('instanceDashOffsets');
389
+ }
200
390
  if (layerType === 'scatterplot' || layerType === 'textBackground') {
201
391
  const pathStyleProps: SDFDashStyleProps = {
202
392
  dashGapPickable: Boolean(this.props.dashGapPickable)
@@ -205,13 +395,129 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
205
395
  } else {
206
396
  const pathStyleProps: PathStyleProps = {
207
397
  dashAlignMode: this.props.dashJustified ? 1 : 0,
208
- dashGapPickable: Boolean(this.props.dashGapPickable)
398
+ dashGapPickable: Boolean(this.props.dashGapPickable),
399
+ dashUnits: DASH_UNITS[this.props.dashUnits ?? 'widths']
209
400
  };
210
401
  this.setShaderModuleProps({pathStyle: pathStyleProps});
211
402
  }
212
403
  }
213
404
  }
214
405
 
406
+ private calculateDashMetrics(
407
+ this: Layer<PathStyleExtensionProps>,
408
+ attribute: Attribute,
409
+ {startRow, endRow}: {startRow: number; endRow: number}
410
+ ): void {
411
+ const pathTesselator = (this.state as any).pathTesselator;
412
+ const vertexStarts = pathTesselator?.vertexStarts as number[] | undefined;
413
+ const instanceCount = pathTesselator?.instanceCount as number | undefined;
414
+ const packedPositions = pathTesselator?.get('positions');
415
+
416
+ if (
417
+ !vertexStarts ||
418
+ instanceCount === undefined ||
419
+ typeof pathTesselator?.getPathSegmentIndices !== 'function'
420
+ ) {
421
+ throw new Error('PathStyleExtension requires PathLayer tessellation data for path dashes.');
422
+ }
423
+
424
+ const binaryPath = (this.props.data as any)?.attributes?.getPath;
425
+ const binaryValue = ArrayBuffer.isView(binaryPath) ? binaryPath : binaryPath?.value;
426
+ const hasReadableBinaryPath = ArrayBuffer.isView(binaryValue);
427
+ // With `_pathType`, WebGL binds binary positions directly and the tesselator's allocated
428
+ // positions array is only a zero-filled placeholder. WebGPU materializes packed neighboring
429
+ // positions when the binary source is CPU-readable; GPU-only sources are rejected below.
430
+ const usePackedPositions =
431
+ packedPositions && (!binaryPath || pathTesselator.normalize || pathTesselator.opts?.isWebGPU);
432
+ if ((binaryPath && !hasReadableBinaryPath) || (!usePackedPositions && !hasReadableBinaryPath)) {
433
+ throw new Error(
434
+ 'PathStyleExtension cannot calculate whole-path dash metrics from GPU-only getPath data; ' +
435
+ 'supply data.attributes.instanceDashOffsets.'
436
+ );
437
+ }
438
+
439
+ const output = attribute.value as Float32Array;
440
+ const metricSize = attribute.size;
441
+ const firstRow = Math.max(0, startRow);
442
+ const lastRow = Math.min(endRow, vertexStarts.length - 1);
443
+
444
+ let binarySize = 3;
445
+ let binaryStride = 3;
446
+ let binaryOffset = 0;
447
+ if (!usePackedPositions) {
448
+ const bytesPerElement = (binaryValue as any).BYTES_PER_ELEMENT;
449
+ binarySize = binaryPath?.size || (this.props.positionFormat === 'XY' ? 2 : 3);
450
+ binaryStride = binaryPath?.stride ? binaryPath.stride / bytesPerElement : binarySize;
451
+ binaryOffset = binaryPath?.offset ? binaryPath.offset / bytesPerElement : 0;
452
+ }
453
+
454
+ const sourcePosition = new Array(3);
455
+ const projectUniforms = project.getUniforms({
456
+ viewport: this.context.viewport,
457
+ modelMatrix: this.props.modelMatrix,
458
+ coordinateSystem: this.props.coordinateSystem,
459
+ coordinateOrigin: this.props.coordinateOrigin,
460
+ // PathLayer normalizes/cuts wrapped paths on the CPU; its shader never wraps again.
461
+ autoWrapLongitude: this.wrapLongitude
462
+ }) as ProjectUniforms;
463
+ const readPosition = (vertexIndex: number): number[] => {
464
+ if (usePackedPositions) {
465
+ sourcePosition[0] = packedPositions[vertexIndex * 3];
466
+ sourcePosition[1] = packedPositions[vertexIndex * 3 + 1];
467
+ sourcePosition[2] = packedPositions[vertexIndex * 3 + 2];
468
+ } else {
469
+ const sourceIndex = binaryOffset + vertexIndex * binaryStride;
470
+ sourcePosition[0] = binaryValue[sourceIndex];
471
+ sourcePosition[1] = binaryValue[sourceIndex + 1];
472
+ sourcePosition[2] = binarySize === 3 ? binaryValue[sourceIndex + 2] : 0;
473
+ }
474
+ return sourcePosition;
475
+ };
476
+
477
+ attribute.startIndices = vertexStarts;
478
+ for (let row = firstRow; row < lastRow; row++) {
479
+ const rowStart = vertexStarts[row];
480
+ const rowEnd = Math.min(vertexStarts[row + 1] ?? instanceCount, instanceCount);
481
+ output.fill(0, rowStart * metricSize, rowEnd * metricSize);
482
+
483
+ const validSegments = pathTesselator.getPathSegmentIndices(row);
484
+
485
+ let phase = 0;
486
+ let previousSegment = -2;
487
+ let previousEnd: [number, number, number] | null = null;
488
+ for (const segmentIndex of validSegments) {
489
+ const outputIndex = segmentIndex * metricSize;
490
+ output[outputIndex] = phase;
491
+
492
+ const start =
493
+ segmentIndex === previousSegment + 1 && previousEnd
494
+ ? previousEnd
495
+ : projectRenderedPathPosition(this, readPosition(segmentIndex), projectUniforms);
496
+ const end = projectRenderedPathPosition(
497
+ this,
498
+ readPosition(segmentIndex + 1),
499
+ projectUniforms
500
+ );
501
+ phase += vec3.dist(start, end);
502
+ previousSegment = segmentIndex;
503
+ previousEnd = end;
504
+ }
505
+
506
+ if (metricSize > 1) {
507
+ for (let vertexIndex = rowStart; vertexIndex < rowEnd; vertexIndex++) {
508
+ output[vertexIndex * metricSize + 1] = phase;
509
+ }
510
+ }
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Calculates the distance from the start of a path to each rendered segment.
516
+ *
517
+ * The final entry is zero because PathLayer reserves the final vertex as invalid padding.
518
+ * This scalar return shape is retained for compatibility; path-mode rendering uses an
519
+ * internal attribute containing both each segment offset and the total path length.
520
+ */
215
521
  getDashOffsets(this: Layer<PathStyleExtensionProps>, path: number[] | number[][]): number[] {
216
522
  const result = [0];
217
523
  const positionSize = this.props.positionFormat === 'XY' ? 2 : 3;
@@ -222,7 +528,7 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
222
528
  let prevP;
223
529
  for (let i = 0; i < geometrySize - 1; i++) {
224
530
  p = isNested ? path[i] : path.slice(i * positionSize, i * positionSize + positionSize);
225
- p = this.projectPosition(p);
531
+ p = this.projectPosition(p, {autoOffset: false});
226
532
 
227
533
  if (i > 0) {
228
534
  result[i] = result[i - 1] + vec3.dist(prevP, p);