@deck.gl/extensions 9.3.0-alpha.6 → 9.3.0-beta.2

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Plug-and-play functionalities for deck.gl layers",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "9.3.0-alpha.6",
6
+ "version": "9.3.0-beta.2",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -47,5 +47,5 @@
47
47
  "@luma.gl/core": "~9.3.2",
48
48
  "@luma.gl/engine": "~9.3.2"
49
49
  },
50
- "gitHead": "6aff0d4eccd01a06a388ab1c07042a94daa3eb31"
50
+ "gitHead": "f150e74afeba9a692974ba41110a5b865ac4787b"
51
51
  }
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {LayerExtension, COORDINATE_SYSTEM} from '@deck.gl/core';
5
+ import {LayerExtension} from '@deck.gl/core';
6
6
  import project64 from './project64';
7
7
 
8
8
  import type {Layer} from '@deck.gl/core';
@@ -13,10 +13,7 @@ export default class Fp64Extension extends LayerExtension {
13
13
 
14
14
  getShaders(this: Layer): any {
15
15
  const {coordinateSystem} = this.props;
16
- if (
17
- coordinateSystem !== COORDINATE_SYSTEM.LNGLAT &&
18
- coordinateSystem !== COORDINATE_SYSTEM.DEFAULT
19
- ) {
16
+ if (coordinateSystem !== 'lnglat' && coordinateSystem !== 'default') {
20
17
  throw new Error('fp64: coordinateSystem must be LNGLAT');
21
18
  }
22
19
 
@@ -4,7 +4,13 @@
4
4
 
5
5
  import {LayerExtension, _mergeShaders as mergeShaders} from '@deck.gl/core';
6
6
  import {vec3} from '@math.gl/core';
7
- import {dashShaders, Defines, offsetShaders} from './shaders.glsl';
7
+ import {
8
+ dashShaders,
9
+ Defines,
10
+ offsetShaders,
11
+ scatterplotDashShaders,
12
+ textBackgroundDashShaders
13
+ } from './shaders.glsl';
8
14
 
9
15
  import type {Accessor, Layer, LayerContext, UpdateParameters} from '@deck.gl/core';
10
16
  import type {ShaderModule} from '@luma.gl/shadertools';
@@ -21,6 +27,10 @@ type PathStyleProps = {
21
27
  dashGapPickable: boolean;
22
28
  };
23
29
 
30
+ type SDFDashStyleProps = {
31
+ dashGapPickable: boolean;
32
+ };
33
+
24
34
  export type PathStyleExtensionProps<DataT = any> = {
25
35
  /**
26
36
  * Accessor for the dash array to draw each path with: `[dashSize, gapSize]` relative to the width of the path.
@@ -63,7 +73,9 @@ export type PathStyleExtensionOptions = {
63
73
  highPrecisionDash: boolean;
64
74
  };
65
75
 
66
- /** Adds selected features to the `PathLayer` and composite layers that render the `PathLayer`. */
76
+ type LayerType = 'path' | 'scatterplot' | 'textBackground';
77
+
78
+ /** Adds selected features to the `PathLayer`, `ScatterplotLayer`, `TextBackgroundLayer`, and composite layers that render them. */
67
79
  export default class PathStyleExtension extends LayerExtension<PathStyleExtensionOptions> {
68
80
  static defaultProps = defaultProps;
69
81
  static extensionName = 'PathStyleExtension';
@@ -76,16 +88,49 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
76
88
  super({dash: dash || highPrecisionDash, offset, highPrecisionDash});
77
89
  }
78
90
 
91
+ private getLayerType(layer: Layer): LayerType | null {
92
+ if ('pathTesselator' in layer.state) {
93
+ return 'path';
94
+ }
95
+ const layerName = (layer.constructor as any).layerName;
96
+ if (layerName === 'ScatterplotLayer') {
97
+ return 'scatterplot';
98
+ }
99
+ if (layerName === 'TextBackgroundLayer') {
100
+ return 'textBackground';
101
+ }
102
+ return null;
103
+ }
104
+
79
105
  isEnabled(layer: Layer<PathStyleExtensionProps>): boolean {
80
- return 'pathTesselator' in layer.state;
106
+ return this.getLayerType(layer) !== null;
81
107
  }
82
108
 
83
109
  getShaders(this: Layer<PathStyleExtensionProps>, extension: this): any {
84
- if (!extension.isEnabled(this)) {
110
+ const layerType = extension.getLayerType(this);
111
+ if (!layerType) {
85
112
  return null;
86
113
  }
87
114
 
88
- // Merge shader injection
115
+ if (layerType === 'scatterplot' || layerType === 'textBackground') {
116
+ if (!extension.opts.dash) {
117
+ return null;
118
+ }
119
+ const inject =
120
+ layerType === 'scatterplot'
121
+ ? scatterplotDashShaders.inject
122
+ : textBackgroundDashShaders.inject;
123
+ const pathStyle: ShaderModule<SDFDashStyleProps> = {
124
+ name: 'pathStyle',
125
+ inject,
126
+ uniformTypes: {
127
+ dashGapPickable: 'i32'
128
+ }
129
+ };
130
+ return {modules: [pathStyle]};
131
+ }
132
+
133
+ // PathLayer: existing logic
89
134
  let result = {} as {inject: Record<string, string>};
90
135
  const defines: Defines = {};
91
136
  if (extension.opts.dash) {
@@ -115,15 +160,15 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
115
160
 
116
161
  initializeState(this: Layer<PathStyleExtensionProps>, context: LayerContext, extension: this) {
117
162
  const attributeManager = this.getAttributeManager();
118
- if (!attributeManager || !extension.isEnabled(this)) {
119
- // This extension only works with the PathLayer
163
+ const layerType = extension.getLayerType(this);
164
+ if (!attributeManager || !layerType) {
120
165
  return;
121
166
  }
122
167
 
123
168
  if (extension.opts.dash) {
124
169
  attributeManager.addInstanced({
125
170
  instanceDashArrays: {size: 2, accessor: 'getDashArray'},
126
- ...(extension.opts.highPrecisionDash
171
+ ...(layerType === 'path' && extension.opts.highPrecisionDash
127
172
  ? {
128
173
  instanceDashOffsets: {
129
174
  size: 1,
@@ -134,7 +179,7 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
134
179
  : {})
135
180
  });
136
181
  }
137
- if (extension.opts.offset) {
182
+ if (layerType === 'path' && extension.opts.offset) {
138
183
  attributeManager.addInstanced({
139
184
  instanceOffsets: {size: 1, accessor: 'getOffset'}
140
185
  });
@@ -151,11 +196,19 @@ export default class PathStyleExtension extends LayerExtension<PathStyleExtensio
151
196
  }
152
197
 
153
198
  if (extension.opts.dash) {
154
- const pathStyleProps: PathStyleProps = {
155
- dashAlignMode: this.props.dashJustified ? 1 : 0,
156
- dashGapPickable: Boolean(this.props.dashGapPickable)
157
- };
158
- this.setShaderModuleProps({pathStyle: pathStyleProps});
199
+ const layerType = extension.getLayerType(this);
200
+ if (layerType === 'scatterplot' || layerType === 'textBackground') {
201
+ const pathStyleProps: SDFDashStyleProps = {
202
+ dashGapPickable: Boolean(this.props.dashGapPickable)
203
+ };
204
+ this.setShaderModuleProps({pathStyle: pathStyleProps});
205
+ } else {
206
+ const pathStyleProps: PathStyleProps = {
207
+ dashAlignMode: this.props.dashJustified ? 1 : 0,
208
+ dashGapPickable: Boolean(this.props.dashGapPickable)
209
+ };
210
+ this.setShaderModuleProps({pathStyle: pathStyleProps});
211
+ }
159
212
  }
160
213
  }
161
214
 
@@ -88,6 +88,219 @@ in float vDashOffset;
88
88
  }
89
89
  };
90
90
 
91
+ export const scatterplotDashShaders = {
92
+ inject: {
93
+ 'vs:#decl': `
94
+ in vec2 instanceDashArrays;
95
+ out vec2 vDashArray;
96
+ `,
97
+
98
+ 'vs:#main-end': `
99
+ vDashArray = instanceDashArrays;
100
+ `,
101
+
102
+ 'fs:#decl': `
103
+ layout(std140) uniform pathStyleUniforms {
104
+ bool dashGapPickable;
105
+ } pathStyle;
106
+
107
+ in vec2 vDashArray;
108
+
109
+ #define PI 3.141592653589793
110
+ `,
111
+
112
+ 'fs:#main-start': `
113
+ bool inDashGap = false;
114
+ float dashUnitLength = vDashArray.x + vDashArray.y;
115
+ if (dashUnitLength > 0.0 && scatterplot.stroked > 0.5) {
116
+ float _distToCenter = length(unitPosition) * outerRadiusPixels;
117
+ float innerRadius = innerUnitRadius * outerRadiusPixels;
118
+ if (_distToCenter >= innerRadius) {
119
+ float strokeWidth = (1.0 - innerUnitRadius) * outerRadiusPixels;
120
+ float midStrokeRadius = (innerUnitRadius + 1.0) * 0.5 * outerRadiusPixels;
121
+ float angle = atan(unitPosition.y, unitPosition.x) + PI;
122
+ float circumference = 2.0 * PI * midStrokeRadius;
123
+ float posAlongStroke = (angle / (2.0 * PI)) * circumference / strokeWidth;
124
+ float unitOffset = mod(posAlongStroke, dashUnitLength);
125
+ if (unitOffset > vDashArray.x) {
126
+ if (scatterplot.filled > 0.5) {
127
+ inDashGap = true;
128
+ } else {
129
+ if (!(pathStyle.dashGapPickable && bool(picking.isActive))) {
130
+ discard;
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ `,
137
+
138
+ 'fs:#main-end': `
139
+ if (inDashGap) {
140
+ float alphaFactor = fragColor.a / max(vLineColor.a, 0.001);
141
+ fragColor = vec4(vFillColor.rgb, vFillColor.a * alphaFactor);
142
+ fragColor = picking_filterPickingColor(fragColor);
143
+ fragColor = picking_filterHighlightColor(fragColor);
144
+ }
145
+ `
146
+ }
147
+ };
148
+
149
+ export const textBackgroundDashShaders = {
150
+ inject: {
151
+ 'vs:#decl': `
152
+ in vec2 instanceDashArrays;
153
+ out vec2 vDashArray;
154
+ `,
155
+
156
+ 'vs:#main-end': `
157
+ vDashArray = instanceDashArrays;
158
+ `,
159
+
160
+ 'fs:#decl': `
161
+ layout(std140) uniform pathStyleUniforms {
162
+ bool dashGapPickable;
163
+ } pathStyle;
164
+
165
+ in vec2 vDashArray;
166
+
167
+ #define PI 3.141592653589793
168
+
169
+ // Calculate position along rounded rectangle perimeter in stroke-width units
170
+ float getPerimeterPosition(vec2 fragUV, vec2 dims, vec4 radii, float lineWidth) {
171
+ float width = dims.x;
172
+ float height = dims.y;
173
+
174
+ float maxRadius = min(width, height) * 0.5;
175
+ float rBL = min(radii.w, maxRadius);
176
+ float rTL = min(radii.z, maxRadius);
177
+ float rTR = min(radii.x, maxRadius);
178
+ float rBR = min(radii.y, maxRadius);
179
+
180
+ vec2 p = fragUV * dims;
181
+
182
+ float leftLen = height - rBL - rTL;
183
+ float topLen = width - rTL - rTR;
184
+ float rightLen = height - rTR - rBR;
185
+ float bottomLen = width - rBR - rBL;
186
+
187
+ float arcBL = PI * 0.5 * rBL;
188
+ float arcTL = PI * 0.5 * rTL;
189
+ float arcTR = PI * 0.5 * rTR;
190
+ float arcBR = PI * 0.5 * rBR;
191
+
192
+ float pos = 0.0;
193
+
194
+ float distLeft = p.x;
195
+ float distRight = width - p.x;
196
+ float distBottom = p.y;
197
+ float distTop = height - p.y;
198
+ float minDist = min(min(distLeft, distRight), min(distBottom, distTop));
199
+
200
+ if (p.x < rBL && p.y < rBL) {
201
+ vec2 c = vec2(rBL, rBL);
202
+ vec2 d = p - c;
203
+ float angle = atan(-d.x, -d.y);
204
+ pos = angle / (PI * 0.5) * arcBL;
205
+ } else if (p.x < rTL && p.y > height - rTL) {
206
+ vec2 c = vec2(rTL, height - rTL);
207
+ vec2 d = p - c;
208
+ float angle = atan(d.y, -d.x);
209
+ pos = arcBL + leftLen + angle / (PI * 0.5) * arcTL;
210
+ } else if (p.x > width - rTR && p.y > height - rTR) {
211
+ vec2 c = vec2(width - rTR, height - rTR);
212
+ vec2 d = p - c;
213
+ float angle = atan(d.x, d.y);
214
+ pos = arcBL + leftLen + arcTL + topLen + angle / (PI * 0.5) * arcTR;
215
+ } else if (p.x > width - rBR && p.y < rBR) {
216
+ vec2 c = vec2(width - rBR, rBR);
217
+ vec2 d = p - c;
218
+ float angle = atan(-d.y, d.x);
219
+ pos = arcBL + leftLen + arcTL + topLen + arcTR + rightLen + angle / (PI * 0.5) * arcBR;
220
+ } else if (minDist == distLeft) {
221
+ pos = arcBL + clamp(p.y - rBL, 0.0, leftLen);
222
+ } else if (minDist == distTop) {
223
+ pos = arcBL + leftLen + arcTL + clamp(p.x - rTL, 0.0, topLen);
224
+ } else if (minDist == distRight) {
225
+ pos = arcBL + leftLen + arcTL + topLen + arcTR + clamp(height - rTR - p.y, 0.0, rightLen);
226
+ } else {
227
+ pos = arcBL + leftLen + arcTL + topLen + arcTR + rightLen + arcBR + clamp(width - rBR - p.x, 0.0, bottomLen);
228
+ }
229
+
230
+ return pos / lineWidth;
231
+ }
232
+
233
+ // Simple rectangular perimeter calculation (no rounded corners)
234
+ float getRectPerimeterPosition(vec2 fragUV, vec2 dims, float lineWidth) {
235
+ float width = dims.x;
236
+ float height = dims.y;
237
+
238
+ float distLeft = fragUV.x * width;
239
+ float distRight = (1.0 - fragUV.x) * width;
240
+ float distBottom = fragUV.y * height;
241
+ float distTop = (1.0 - fragUV.y) * height;
242
+
243
+ float minDist = min(min(distLeft, distRight), min(distBottom, distTop));
244
+
245
+ float pos = 0.0;
246
+ if (minDist == distLeft) {
247
+ pos = fragUV.y * height;
248
+ } else if (minDist == distTop) {
249
+ pos = height + fragUV.x * width;
250
+ } else if (minDist == distRight) {
251
+ pos = height + width + (1.0 - fragUV.y) * height;
252
+ } else {
253
+ pos = 2.0 * height + width + (1.0 - fragUV.x) * width;
254
+ }
255
+
256
+ return pos / lineWidth;
257
+ }
258
+ `,
259
+
260
+ 'fs:#main-start': `
261
+ bool inDashGap = false;
262
+ float dashUnitLength = vDashArray.x + vDashArray.y;
263
+ if (dashUnitLength > 0.0 && textBackground.stroked) {
264
+ float distToEdge;
265
+ bool hasRoundedCorners = textBackground.borderRadius != vec4(0.0);
266
+ if (hasRoundedCorners) {
267
+ distToEdge = round_rect(uv, dimensions, textBackground.borderRadius);
268
+ } else {
269
+ distToEdge = rect(uv, dimensions);
270
+ }
271
+
272
+ if (distToEdge <= vLineWidth && distToEdge >= 0.0) {
273
+ float posAlongStroke;
274
+ if (hasRoundedCorners) {
275
+ posAlongStroke = getPerimeterPosition(uv, dimensions, textBackground.borderRadius, vLineWidth);
276
+ } else {
277
+ posAlongStroke = getRectPerimeterPosition(uv, dimensions, vLineWidth);
278
+ }
279
+ float unitOffset = mod(posAlongStroke, dashUnitLength);
280
+ if (unitOffset > vDashArray.x) {
281
+ if (vFillColor.a > 0.0) {
282
+ inDashGap = true;
283
+ } else {
284
+ if (!(pathStyle.dashGapPickable && bool(picking.isActive))) {
285
+ discard;
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+ `,
292
+
293
+ 'fs:#main-end': `
294
+ if (inDashGap) {
295
+ float alphaFactor = fragColor.a / max(vLineColor.a, 0.001);
296
+ fragColor = vec4(vFillColor.rgb, vFillColor.a * alphaFactor);
297
+ fragColor = picking_filterPickingColor(fragColor);
298
+ fragColor = picking_filterHighlightColor(fragColor);
299
+ }
300
+ `
301
+ }
302
+ };
303
+
91
304
  export const offsetShaders = {
92
305
  inject: {
93
306
  'vs:#decl': `