@deck.gl/extensions 9.0.0-beta.3 → 9.0.0-beta.5

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.0.0-beta.3",
6
+ "version": "9.0.0-beta.5",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -51,5 +51,5 @@
51
51
  "@math.gl/core": "^4.0.0",
52
52
  "@math.gl/web-mercator": "^4.0.0"
53
53
  },
54
- "gitHead": "643d1009268bbf92b5bc0941d00aa59d5e3a582b"
54
+ "gitHead": "02588aba7cbf25d48cec20b30a686bba9be32bff"
55
55
  }
@@ -22,27 +22,36 @@ import {GL} from '@luma.gl/constants';
22
22
  import type {Framebuffer} from '@luma.gl/core';
23
23
  import type {Model} from '@luma.gl/engine';
24
24
  import type {Layer, LayerContext, Accessor, UpdateParameters} from '@deck.gl/core';
25
- import {LayerExtension} from '@deck.gl/core';
25
+ import {_deepEqual as deepEqual, LayerExtension, log} from '@deck.gl/core';
26
26
  import {shaderModule, shaderModule64} from './shader-module';
27
27
  import * as aggregator from './aggregator';
28
28
 
29
29
  const defaultProps = {
30
30
  getFilterValue: {type: 'accessor', value: 0},
31
+ getFilterCategory: {type: 'accessor', value: 0},
31
32
  onFilteredItemsChange: {type: 'function', value: null, optional: true},
32
33
 
33
34
  filterEnabled: true,
34
35
  filterRange: [-1, 1],
35
36
  filterSoftRange: null,
37
+ filterCategories: [0],
36
38
  filterTransformSize: true,
37
39
  filterTransformColor: true
38
40
  };
39
41
 
42
+ type FilterCategory = number | string;
43
+
40
44
  export type DataFilterExtensionProps<DataT = any> = {
41
45
  /**
42
46
  * Accessor to retrieve the value for each object that it will be filtered by.
43
47
  * Returns either a number (if `filterSize: 1`) or an array of numbers.
44
48
  */
45
49
  getFilterValue?: Accessor<DataT, number | number[]>;
50
+ /**
51
+ * Accessor to retrieve the category (`number | string`) for each object that it will be filtered by.
52
+ * Returns either a single category (if `filterSize: 1`) or an array of categories.
53
+ */
54
+ getFilterCategory?: Accessor<DataT, FilterCategory | FilterCategory[]>;
46
55
  /**
47
56
  * Enable/disable the data filter. If the data filter is disabled, all objects are rendered.
48
57
  * @default true
@@ -70,6 +79,11 @@ export type DataFilterExtensionProps<DataT = any> = {
70
79
  * @default true
71
80
  */
72
81
  filterTransformColor?: boolean;
82
+ /**
83
+ * The categories which define whether an object should be rendered.
84
+ * @default []
85
+ */
86
+ filterCategories: FilterCategory[] | FilterCategory[][];
73
87
  /**
74
88
  * Only called if the `countItems` option is enabled.
75
89
  */
@@ -82,21 +96,33 @@ export type DataFilterExtensionProps<DataT = any> = {
82
96
  };
83
97
 
84
98
  type DataFilterExtensionOptions = {
99
+ /**
100
+ * The size of the category filter (number of columns to filter by). The category filter can show/hide data based on 1-4 properties of each object.
101
+ * @default 1
102
+ */
103
+ categorySize?: 1 | 2 | 3 | 4;
85
104
  /**
86
105
  * The size of the filter (number of columns to filter by). The data filter can show/hide data based on 1-4 numeric properties of each object.
87
106
  * @default 1
88
107
  */
89
- filterSize: number;
108
+ filterSize?: 1 | 2 | 3 | 4;
90
109
  /**
91
110
  * Use 64-bit precision instead of 32-bit.
92
111
  * @default false
93
112
  */
94
- fp64: boolean;
113
+ fp64?: boolean;
95
114
  /**
96
115
  * If `true`, reports the number of filtered objects with the `onFilteredItemsChange` callback.
97
116
  * @default `false`.
98
117
  */
99
- countItems: boolean;
118
+ countItems?: boolean;
119
+ };
120
+
121
+ const defaultOptions: Required<DataFilterExtensionOptions> = {
122
+ categorySize: 1,
123
+ filterSize: 1,
124
+ fp64: false,
125
+ countItems: false
100
126
  };
101
127
 
102
128
  const DATA_TYPE_FROM_SIZE = {
@@ -107,28 +133,24 @@ const DATA_TYPE_FROM_SIZE = {
107
133
  };
108
134
 
109
135
  /** Adds GPU-based data filtering functionalities to layers. It allows the layer to show/hide objects based on user-defined properties. */
110
- export default class DataFilterExtension extends LayerExtension<DataFilterExtensionOptions> {
136
+ export default class DataFilterExtension extends LayerExtension<
137
+ Required<DataFilterExtensionOptions>
138
+ > {
111
139
  static defaultProps = defaultProps;
112
140
  static extensionName = 'DataFilterExtension';
113
141
 
114
- constructor({
115
- filterSize = 1,
116
- fp64 = false,
117
- countItems = false
118
- }: Partial<DataFilterExtensionOptions> = {}) {
119
- if (!DATA_TYPE_FROM_SIZE[filterSize]) {
120
- throw new Error('filterSize out of range');
121
- }
122
-
123
- super({filterSize, fp64, countItems});
142
+ constructor(opts: DataFilterExtensionOptions) {
143
+ super({...defaultOptions, ...opts});
124
144
  }
125
145
 
126
146
  getShaders(this: Layer<DataFilterExtensionProps>, extension: this): any {
127
- const {filterSize, fp64} = extension.opts;
147
+ const {categorySize, filterSize, fp64} = extension.opts;
128
148
 
129
149
  return {
130
150
  modules: [fp64 ? shaderModule64 : shaderModule],
131
151
  defines: {
152
+ DATACATEGORY_TYPE: DATA_TYPE_FROM_SIZE[categorySize],
153
+ DATACATEGORY_CHANNELS: categorySize,
132
154
  DATAFILTER_TYPE: DATA_TYPE_FROM_SIZE[filterSize],
133
155
  DATAFILTER_DOUBLE: Boolean(fp64)
134
156
  }
@@ -137,11 +159,13 @@ export default class DataFilterExtension extends LayerExtension<DataFilterExtens
137
159
 
138
160
  initializeState(this: Layer<DataFilterExtensionProps>, context: LayerContext, extension: this) {
139
161
  const attributeManager = this.getAttributeManager();
162
+ const {categorySize, filterSize, fp64} = extension.opts;
163
+
140
164
  if (attributeManager) {
141
165
  attributeManager.add({
142
166
  filterValues: {
143
- size: extension.opts.filterSize,
144
- type: extension.opts.fp64 ? GL.DOUBLE : GL.FLOAT,
167
+ size: filterSize,
168
+ type: fp64 ? GL.DOUBLE : GL.FLOAT,
145
169
  accessor: 'getFilterValue',
146
170
  shaderAttributes: {
147
171
  filterValues: {
@@ -151,6 +175,23 @@ export default class DataFilterExtension extends LayerExtension<DataFilterExtens
151
175
  divisor: 1
152
176
  }
153
177
  }
178
+ },
179
+ filterCategoryValues: {
180
+ size: categorySize,
181
+ type: GL.FLOAT,
182
+ accessor: 'getFilterCategory',
183
+ transform:
184
+ categorySize === 1
185
+ ? d => extension._getCategoryKey.call(this, d, 0)
186
+ : d => d.map((x, i) => extension._getCategoryKey.call(this, x, i)),
187
+ shaderAttributes: {
188
+ filterCategoryValues: {
189
+ divisor: 0
190
+ },
191
+ instanceFilterCategoryValues: {
192
+ divisor: 1
193
+ }
194
+ }
154
195
  }
155
196
  });
156
197
  }
@@ -194,31 +235,60 @@ export default class DataFilterExtension extends LayerExtension<DataFilterExtens
194
235
 
195
236
  updateState(
196
237
  this: Layer<DataFilterExtensionProps>,
197
- {props, oldProps}: UpdateParameters<Layer<DataFilterExtensionProps>>
238
+ {props, oldProps, changeFlags}: UpdateParameters<Layer<DataFilterExtensionProps>>,
239
+ extension: this
198
240
  ) {
241
+ const attributeManager = this.getAttributeManager();
242
+ const {categorySize} = extension.opts;
199
243
  if (this.state.filterModel) {
200
- const attributeManager = this.getAttributeManager();
201
244
  const filterNeedsUpdate =
202
245
  // attributeManager must be defined for filterModel to be set
203
246
  attributeManager!.attributes.filterValues.needsUpdate() ||
247
+ attributeManager!.attributes.filterCategoryValues?.needsUpdate() ||
204
248
  props.filterEnabled !== oldProps.filterEnabled ||
205
249
  props.filterRange !== oldProps.filterRange ||
206
- props.filterSoftRange !== oldProps.filterSoftRange;
250
+ props.filterSoftRange !== oldProps.filterSoftRange ||
251
+ props.filterCategories !== oldProps.filterCategories;
207
252
  if (filterNeedsUpdate) {
208
253
  this.setState({filterNeedsUpdate});
209
254
  }
210
255
  }
256
+ if (attributeManager?.attributes.filterCategoryValues) {
257
+ // Update bitmask if accessor or selected categories has changed
258
+ const categoryBitMaskNeedsUpdate =
259
+ attributeManager.attributes.filterCategoryValues.needsUpdate() ||
260
+ !deepEqual(props.filterCategories, oldProps.filterCategories, 2);
261
+ if (categoryBitMaskNeedsUpdate) {
262
+ this.setState({categoryBitMaskNeedsUpdate});
263
+ }
264
+
265
+ // Need to recreate category map if categorySize has changed
266
+ const resetCategories = changeFlags.dataChanged;
267
+ if (resetCategories) {
268
+ this.setState({
269
+ categoryMap: Array(categorySize)
270
+ .fill(0)
271
+ .map(() => ({}))
272
+ });
273
+ attributeManager.attributes.filterCategoryValues.setNeedsUpdate('categoryMap');
274
+ }
275
+ }
211
276
  }
212
277
 
213
278
  draw(this: Layer<DataFilterExtensionProps>, params: any, extension: this) {
214
279
  const filterFBO = this.state.filterFBO as Framebuffer;
215
280
  const filterModel = this.state.filterModel as Model;
216
281
  const filterNeedsUpdate = this.state.filterNeedsUpdate as boolean;
282
+ const categoryBitMaskNeedsUpdate = this.state.categoryBitMaskNeedsUpdate as boolean;
217
283
 
218
284
  const {onFilteredItemsChange} = this.props;
285
+
286
+ if (categoryBitMaskNeedsUpdate) {
287
+ extension._updateCategoryBitMask.call(this, params, extension);
288
+ }
219
289
  if (filterNeedsUpdate && onFilteredItemsChange && filterModel) {
220
290
  const {
221
- attributes: {filterValues, filterIndices}
291
+ attributes: {filterValues, filterCategoryValues, filterIndices}
222
292
  } = this.getAttributeManager()!;
223
293
  filterModel.setVertexCount(this.getNumInstances());
224
294
 
@@ -228,8 +298,10 @@ export default class DataFilterExtension extends LayerExtension<DataFilterExtens
228
298
  // @ts-expect-error filterValue and filterIndices should always have buffer value
229
299
  filterModel.setAttributes({
230
300
  ...filterValues.getValue(),
301
+ ...filterCategoryValues?.getValue(),
231
302
  ...filterIndices?.getValue()
232
303
  });
304
+ filterModel.setUniforms(params.uniforms);
233
305
  filterModel.device.withParametersWebGL(
234
306
  {
235
307
  framebuffer: filterFBO,
@@ -260,4 +332,54 @@ export default class DataFilterExtension extends LayerExtension<DataFilterExtens
260
332
  filterFBO?.destroy();
261
333
  filterModel?.destroy();
262
334
  }
335
+
336
+ /**
337
+ * Updates the bitmask used on the GPU to perform the filter based on the
338
+ * `filterCategories` prop. The mapping between categories and bit in the bitmask
339
+ * is performed by `_getCategoryKey()`
340
+ */
341
+ _updateCategoryBitMask(
342
+ this: Layer<DataFilterExtensionProps>,
343
+ params: any,
344
+ extension: this
345
+ ): void {
346
+ const {categorySize} = extension.opts;
347
+ const {filterCategories} = this.props;
348
+ const categoryBitMask = new Uint32Array([0, 0, 0, 0]);
349
+ const categoryFilters = (
350
+ categorySize === 1 ? [filterCategories] : filterCategories
351
+ ) as FilterCategory[][];
352
+ const maxCategories = categorySize === 1 ? 128 : categorySize === 2 ? 64 : 32;
353
+ for (let c = 0; c < categoryFilters.length; c++) {
354
+ const categoryFilter = categoryFilters[c];
355
+ for (const category of categoryFilter) {
356
+ const key = extension._getCategoryKey.call(this, category, c);
357
+ if (key < maxCategories) {
358
+ const channel = c * (maxCategories / 32) + Math.floor(key / 32);
359
+ categoryBitMask[channel] += Math.pow(2, key % 32); // 1 << key fails for key > 30
360
+ } else {
361
+ log.warn(`Exceeded maximum number of categories (${maxCategories})`)();
362
+ }
363
+ }
364
+ }
365
+ /* eslint-disable-next-line camelcase */
366
+ params.uniforms.filter_categoryBitMask = categoryBitMask;
367
+ this.state.categoryBitMaskNeedsUpdate = false;
368
+ }
369
+
370
+ /**
371
+ * Returns an index of bit in the bitmask for a given category. If the category has
372
+ * not yet been assigned a bit, a new one is assigned.
373
+ */
374
+ _getCategoryKey(
375
+ this: Layer<DataFilterExtensionProps>,
376
+ category: FilterCategory,
377
+ channel: number
378
+ ) {
379
+ const categoryMap = (this.state.categoryMap as Record<FilterCategory, number>[])[channel];
380
+ if (!(category in categoryMap)) {
381
+ categoryMap[category] = Object.keys(categoryMap).length;
382
+ }
383
+ return categoryMap[category];
384
+ }
263
385
  }
@@ -12,13 +12,16 @@ uniform DATAFILTER_TYPE filter_max;
12
12
  uniform bool filter_useSoftMargin;
13
13
  uniform bool filter_enabled;
14
14
  uniform bool filter_transformSize;
15
+ uniform ivec4 filter_categoryBitMask;
15
16
 
16
17
  #ifdef NON_INSTANCED_MODEL
17
18
  #define DATAFILTER_ATTRIB filterValues
18
19
  #define DATAFILTER_ATTRIB_64LOW filterValues64Low
20
+ #define DATACATEGORY_ATTRIB filterCategoryValues
19
21
  #else
20
22
  #define DATAFILTER_ATTRIB instanceFilterValues
21
23
  #define DATAFILTER_ATTRIB_64LOW instanceFilterValues64Low
24
+ #define DATACATEGORY_ATTRIB instanceFilterCategoryValues
22
25
  #endif
23
26
 
24
27
  in DATAFILTER_TYPE DATAFILTER_ATTRIB;
@@ -28,6 +31,7 @@ in DATAFILTER_TYPE DATAFILTER_ATTRIB;
28
31
  uniform DATAFILTER_TYPE filter_min64High;
29
32
  uniform DATAFILTER_TYPE filter_max64High;
30
33
  #endif
34
+ in DATACATEGORY_TYPE DATACATEGORY_ATTRIB;
31
35
 
32
36
  out float dataFilter_value;
33
37
 
@@ -43,7 +47,7 @@ float dataFilter_reduceValue(vec3 value) {
43
47
  float dataFilter_reduceValue(vec4 value) {
44
48
  return min(min(value.x, value.y), min(value.z, value.w));
45
49
  }
46
- void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFromMax) {
50
+ void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFromMax, DATACATEGORY_TYPE category) {
47
51
  if (filter_enabled) {
48
52
  if (filter_useSoftMargin) {
49
53
  dataFilter_value = dataFilter_reduceValue(
@@ -55,6 +59,29 @@ void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFrom
55
59
  step(filter_min, valueFromMin) * step(valueFromMax, filter_max)
56
60
  );
57
61
  }
62
+
63
+ #if DATACATEGORY_CHANNELS == 1 // One 128-bit mask
64
+ int dataFilter_masks = filter_categoryBitMask[int(category / 32.0)];
65
+ #elif DATACATEGORY_CHANNELS == 2 // Two 64-bit masks
66
+ ivec2 dataFilter_masks = ivec2(
67
+ filter_categoryBitMask[int(category.x / 32.0)],
68
+ filter_categoryBitMask[int(category.y / 32.0) + 2]
69
+ );
70
+ #elif DATACATEGORY_CHANNELS == 3 // Three 32-bit masks
71
+ ivec3 dataFilter_masks = filter_categoryBitMask.xyz;
72
+ #else // Four 32-bit masks
73
+ ivec4 dataFilter_masks = filter_categoryBitMask;
74
+ #endif
75
+
76
+ // Shift mask and extract relevant bits
77
+ DATACATEGORY_TYPE dataFilter_bits = DATACATEGORY_TYPE(dataFilter_masks) / pow(DATACATEGORY_TYPE(2.0), mod(category, 32.0));
78
+ dataFilter_bits = mod(floor(dataFilter_bits), 2.0);
79
+
80
+ #if DATACATEGORY_CHANNELS == 1
81
+ if(dataFilter_bits == 0.0) dataFilter_value = 0.0;
82
+ #else
83
+ if(any(equal(dataFilter_bits, DATACATEGORY_TYPE(0.0)))) dataFilter_value = 0.0;
84
+ #endif
58
85
  } else {
59
86
  dataFilter_value = 1.0;
60
87
  }
@@ -138,10 +165,11 @@ const inject = {
138
165
  #ifdef DATAFILTER_DOUBLE
139
166
  dataFilter_setValue(
140
167
  DATAFILTER_ATTRIB - filter_min64High + DATAFILTER_ATTRIB_64LOW,
141
- DATAFILTER_ATTRIB - filter_max64High + DATAFILTER_ATTRIB_64LOW
168
+ DATAFILTER_ATTRIB - filter_max64High + DATAFILTER_ATTRIB_64LOW,
169
+ DATACATEGORY_ATTRIB
142
170
  );
143
171
  #else
144
- dataFilter_setValue(DATAFILTER_ATTRIB, DATAFILTER_ATTRIB);
172
+ dataFilter_setValue(DATAFILTER_ATTRIB, DATAFILTER_ATTRIB, DATACATEGORY_ATTRIB);
145
173
  #endif
146
174
  `,
147
175