@deck.gl/extensions 9.4.0-beta.1 → 9.4.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dist.dev.js +1281 -120
- package/dist/fill-style/fill-style-extension.d.ts +38 -6
- package/dist/fill-style/fill-style-extension.d.ts.map +1 -1
- package/dist/fill-style/fill-style-extension.js +88 -10
- package/dist/fill-style/fill-style-extension.js.map +1 -1
- package/dist/fill-style/procedural-pattern.d.ts +65 -0
- package/dist/fill-style/procedural-pattern.d.ts.map +1 -0
- package/dist/fill-style/procedural-pattern.js +135 -0
- package/dist/fill-style/procedural-pattern.js.map +1 -0
- package/dist/fill-style/shader-module.d.ts +13 -4
- package/dist/fill-style/shader-module.d.ts.map +1 -1
- package/dist/fill-style/shader-module.js +212 -21
- package/dist/fill-style/shader-module.js.map +1 -1
- package/dist/index.cjs +946 -172
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/mask/shader-module.d.ts +1 -0
- package/dist/mask/shader-module.d.ts.map +1 -1
- package/dist/path-style/path-style-extension.d.ts +49 -6
- package/dist/path-style/path-style-extension.d.ts.map +1 -1
- package/dist/path-style/path-style-extension.js +237 -37
- package/dist/path-style/path-style-extension.js.map +1 -1
- package/dist/path-style/shaders.glsl.d.ts +30 -1
- package/dist/path-style/shaders.glsl.d.ts.map +1 -1
- package/dist/path-style/shaders.glsl.js +136 -21
- package/dist/path-style/shaders.glsl.js.map +1 -1
- package/dist/terrain/shader-module.d.ts +1 -0
- package/dist/terrain/shader-module.d.ts.map +1 -1
- package/dist.min.js +377 -98
- package/package.json +6 -6
- package/src/fill-style/fill-style-extension.ts +135 -13
- package/src/fill-style/procedural-pattern.ts +203 -0
- package/src/fill-style/shader-module.ts +233 -23
- package/src/index.ts +10 -1
- package/src/path-style/path-style-extension.ts +350 -44
- 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 {
|
|
6
|
-
|
|
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 {
|
|
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]`
|
|
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
|
|
112
|
+
dash?: boolean;
|
|
64
113
|
/**
|
|
65
114
|
* Add capability to offset lines.
|
|
66
115
|
* @default false
|
|
67
116
|
*/
|
|
68
|
-
offset
|
|
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
|
|
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<
|
|
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
|
-
}:
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|