@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.
@@ -1,6 +1,6 @@
1
1
  import type { ShaderModule } from '@luma.gl/shadertools';
2
2
  import type { Viewport } from '@deck.gl/core';
3
- import type { BrushingExtensionProps } from './brushing-extension';
3
+ import type { BrushingExtensionProps } from "./brushing-extension.js";
4
4
  type BrushingModuleSettings = {
5
5
  viewport: Viewport;
6
6
  mousePosition?: {
@@ -1 +1 @@
1
- {"version":3,"file":"shader-module.d.ts","sourceRoot":"","sources":["../../src/brushing/shader-module.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AAE5C,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,sBAAsB,CAAC;AAEjE,KAAK,sBAAsB,GAAG;IAE5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,CAAC,EAAE;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CACxC,GAAG,sBAAsB,CAAC;;AAkF3B,wBA4B0C"}
1
+ {"version":3,"file":"shader-module.d.ts","sourceRoot":"","sources":["../../src/brushing/shader-module.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AAE5C,OAAO,KAAK,EAAC,sBAAsB,EAAC,gCAA6B;AAEjE,KAAK,sBAAsB,GAAG;IAE5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,aAAa,CAAC,EAAE;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CACxC,GAAG,sBAAsB,CAAC;;AAkF3B,wBA4B0C"}
@@ -1,11 +1,17 @@
1
1
  import type { Layer, LayerContext, Accessor, UpdateParameters } from '@deck.gl/core';
2
2
  import { LayerExtension } from '@deck.gl/core';
3
+ type FilterCategory = number | string;
3
4
  export type DataFilterExtensionProps<DataT = any> = {
4
5
  /**
5
6
  * Accessor to retrieve the value for each object that it will be filtered by.
6
7
  * Returns either a number (if `filterSize: 1`) or an array of numbers.
7
8
  */
8
9
  getFilterValue?: Accessor<DataT, number | number[]>;
10
+ /**
11
+ * Accessor to retrieve the category (`number | string`) for each object that it will be filtered by.
12
+ * Returns either a single category (if `filterSize: 1`) or an array of categories.
13
+ */
14
+ getFilterCategory?: Accessor<DataT, FilterCategory | FilterCategory[]>;
9
15
  /**
10
16
  * Enable/disable the data filter. If the data filter is disabled, all objects are rendered.
11
17
  * @default true
@@ -33,6 +39,11 @@ export type DataFilterExtensionProps<DataT = any> = {
33
39
  * @default true
34
40
  */
35
41
  filterTransformColor?: boolean;
42
+ /**
43
+ * The categories which define whether an object should be rendered.
44
+ * @default []
45
+ */
46
+ filterCategories: FilterCategory[] | FilterCategory[][];
36
47
  /**
37
48
  * Only called if the `countItems` option is enabled.
38
49
  */
@@ -44,29 +55,38 @@ export type DataFilterExtensionProps<DataT = any> = {
44
55
  }) => void;
45
56
  };
46
57
  type DataFilterExtensionOptions = {
58
+ /**
59
+ * 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.
60
+ * @default 1
61
+ */
62
+ categorySize?: 1 | 2 | 3 | 4;
47
63
  /**
48
64
  * 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.
49
65
  * @default 1
50
66
  */
51
- filterSize: number;
67
+ filterSize?: 1 | 2 | 3 | 4;
52
68
  /**
53
69
  * Use 64-bit precision instead of 32-bit.
54
70
  * @default false
55
71
  */
56
- fp64: boolean;
72
+ fp64?: boolean;
57
73
  /**
58
74
  * If `true`, reports the number of filtered objects with the `onFilteredItemsChange` callback.
59
75
  * @default `false`.
60
76
  */
61
- countItems: boolean;
77
+ countItems?: boolean;
62
78
  };
63
79
  /** Adds GPU-based data filtering functionalities to layers. It allows the layer to show/hide objects based on user-defined properties. */
64
- export default class DataFilterExtension extends LayerExtension<DataFilterExtensionOptions> {
80
+ export default class DataFilterExtension extends LayerExtension<Required<DataFilterExtensionOptions>> {
65
81
  static defaultProps: {
66
82
  getFilterValue: {
67
83
  type: string;
68
84
  value: number;
69
85
  };
86
+ getFilterCategory: {
87
+ type: string;
88
+ value: number;
89
+ };
70
90
  onFilteredItemsChange: {
71
91
  type: string;
72
92
  value: null;
@@ -75,16 +95,28 @@ export default class DataFilterExtension extends LayerExtension<DataFilterExtens
75
95
  filterEnabled: boolean;
76
96
  filterRange: number[];
77
97
  filterSoftRange: null;
98
+ filterCategories: number[];
78
99
  filterTransformSize: boolean;
79
100
  filterTransformColor: boolean;
80
101
  };
81
102
  static extensionName: string;
82
- constructor({ filterSize, fp64, countItems }?: Partial<DataFilterExtensionOptions>);
103
+ constructor(opts: DataFilterExtensionOptions);
83
104
  getShaders(this: Layer<DataFilterExtensionProps>, extension: this): any;
84
105
  initializeState(this: Layer<DataFilterExtensionProps>, context: LayerContext, extension: this): void;
85
- updateState(this: Layer<DataFilterExtensionProps>, { props, oldProps }: UpdateParameters<Layer<DataFilterExtensionProps>>): void;
106
+ updateState(this: Layer<DataFilterExtensionProps>, { props, oldProps, changeFlags }: UpdateParameters<Layer<DataFilterExtensionProps>>, extension: this): void;
86
107
  draw(this: Layer<DataFilterExtensionProps>, params: any, extension: this): void;
87
108
  finalizeState(this: Layer<DataFilterExtensionProps>): void;
109
+ /**
110
+ * Updates the bitmask used on the GPU to perform the filter based on the
111
+ * `filterCategories` prop. The mapping between categories and bit in the bitmask
112
+ * is performed by `_getCategoryKey()`
113
+ */
114
+ _updateCategoryBitMask(this: Layer<DataFilterExtensionProps>, params: any, extension: this): void;
115
+ /**
116
+ * Returns an index of bit in the bitmask for a given category. If the category has
117
+ * not yet been assigned a bit, a new one is assigned.
118
+ */
119
+ _getCategoryKey(this: Layer<DataFilterExtensionProps>, category: FilterCategory, channel: number): number;
88
120
  }
89
121
  export {};
90
122
  //# sourceMappingURL=data-filter-extension.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-filter-extension.d.ts","sourceRoot":"","sources":["../../src/data-filter/data-filter-extension.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAC,MAAM,eAAe,CAAC;AACnF,OAAO,EAAC,cAAc,EAAC,MAAM,eAAe,CAAC;AAe7C,MAAM,MAAM,wBAAwB,CAAC,KAAK,GAAG,GAAG,IAAI;IAClD;;;OAGG;IACH,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACpD;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACpD;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI,CAAC;IAC/D;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE;QAC5B,kCAAkC;QAClC,EAAE,EAAE,MAAM,CAAC;QACX,uDAAuD;QACvD,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,IAAI,CAAC;CACZ,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AASF,0IAA0I;AAC1I,MAAM,CAAC,OAAO,OAAO,mBAAoB,SAAQ,cAAc,CAAC,0BAA0B,CAAC;IACzF,MAAM,CAAC,YAAY;;;;;;;;;;;;;;;MAAgB;IACnC,MAAM,CAAC,aAAa,SAAyB;gBAEjC,EACV,UAAc,EACd,IAAY,EACZ,UAAkB,EACnB,GAAE,OAAO,CAAC,0BAA0B,CAAM;IAQ3C,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,wBAAwB,CAAC,EAAE,SAAS,EAAE,IAAI,GAAG,GAAG;IAYvE,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,wBAAwB,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI;IAyD7F,WAAW,CACT,IAAI,EAAE,KAAK,CAAC,wBAAwB,CAAC,EACrC,EAAC,KAAK,EAAE,QAAQ,EAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAgBtE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,wBAAwB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI;IA0CxE,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,wBAAwB,CAAC;CAQpD"}
1
+ {"version":3,"file":"data-filter-extension.d.ts","sourceRoot":"","sources":["../../src/data-filter/data-filter-extension.ts"],"names":[],"mappings":"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"}
@@ -18,18 +18,26 @@
18
18
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  // THE SOFTWARE.
20
20
  import { GL } from '@luma.gl/constants';
21
- import { LayerExtension } from '@deck.gl/core';
21
+ import { _deepEqual as deepEqual, LayerExtension, log } from '@deck.gl/core';
22
22
  import { shaderModule, shaderModule64 } from "./shader-module.js";
23
23
  import * as aggregator from "./aggregator.js";
24
24
  const defaultProps = {
25
25
  getFilterValue: { type: 'accessor', value: 0 },
26
+ getFilterCategory: { type: 'accessor', value: 0 },
26
27
  onFilteredItemsChange: { type: 'function', value: null, optional: true },
27
28
  filterEnabled: true,
28
29
  filterRange: [-1, 1],
29
30
  filterSoftRange: null,
31
+ filterCategories: [0],
30
32
  filterTransformSize: true,
31
33
  filterTransformColor: true
32
34
  };
35
+ const defaultOptions = {
36
+ categorySize: 1,
37
+ filterSize: 1,
38
+ fp64: false,
39
+ countItems: false
40
+ };
33
41
  const DATA_TYPE_FROM_SIZE = {
34
42
  1: 'float',
35
43
  2: 'vec2',
@@ -40,17 +48,16 @@ const DATA_TYPE_FROM_SIZE = {
40
48
  export default class DataFilterExtension extends LayerExtension {
41
49
  static { this.defaultProps = defaultProps; }
42
50
  static { this.extensionName = 'DataFilterExtension'; }
43
- constructor({ filterSize = 1, fp64 = false, countItems = false } = {}) {
44
- if (!DATA_TYPE_FROM_SIZE[filterSize]) {
45
- throw new Error('filterSize out of range');
46
- }
47
- super({ filterSize, fp64, countItems });
51
+ constructor(opts) {
52
+ super({ ...defaultOptions, ...opts });
48
53
  }
49
54
  getShaders(extension) {
50
- const { filterSize, fp64 } = extension.opts;
55
+ const { categorySize, filterSize, fp64 } = extension.opts;
51
56
  return {
52
57
  modules: [fp64 ? shaderModule64 : shaderModule],
53
58
  defines: {
59
+ DATACATEGORY_TYPE: DATA_TYPE_FROM_SIZE[categorySize],
60
+ DATACATEGORY_CHANNELS: categorySize,
54
61
  DATAFILTER_TYPE: DATA_TYPE_FROM_SIZE[filterSize],
55
62
  DATAFILTER_DOUBLE: Boolean(fp64)
56
63
  }
@@ -58,11 +65,12 @@ export default class DataFilterExtension extends LayerExtension {
58
65
  }
59
66
  initializeState(context, extension) {
60
67
  const attributeManager = this.getAttributeManager();
68
+ const { categorySize, filterSize, fp64 } = extension.opts;
61
69
  if (attributeManager) {
62
70
  attributeManager.add({
63
71
  filterValues: {
64
- size: extension.opts.filterSize,
65
- type: extension.opts.fp64 ? GL.DOUBLE : GL.FLOAT,
72
+ size: filterSize,
73
+ type: fp64 ? GL.DOUBLE : GL.FLOAT,
66
74
  accessor: 'getFilterValue',
67
75
  shaderAttributes: {
68
76
  filterValues: {
@@ -72,6 +80,22 @@ export default class DataFilterExtension extends LayerExtension {
72
80
  divisor: 1
73
81
  }
74
82
  }
83
+ },
84
+ filterCategoryValues: {
85
+ size: categorySize,
86
+ type: GL.FLOAT,
87
+ accessor: 'getFilterCategory',
88
+ transform: categorySize === 1
89
+ ? d => extension._getCategoryKey.call(this, d, 0)
90
+ : d => d.map((x, i) => extension._getCategoryKey.call(this, x, i)),
91
+ shaderAttributes: {
92
+ filterCategoryValues: {
93
+ divisor: 0
94
+ },
95
+ instanceFilterCategoryValues: {
96
+ divisor: 1
97
+ }
98
+ }
75
99
  }
76
100
  });
77
101
  }
@@ -106,35 +130,62 @@ export default class DataFilterExtension extends LayerExtension {
106
130
  this.setState({ filterFBO, filterModel });
107
131
  }
108
132
  }
109
- updateState({ props, oldProps }) {
133
+ updateState({ props, oldProps, changeFlags }, extension) {
134
+ const attributeManager = this.getAttributeManager();
135
+ const { categorySize } = extension.opts;
110
136
  if (this.state.filterModel) {
111
- const attributeManager = this.getAttributeManager();
112
137
  const filterNeedsUpdate =
113
138
  // attributeManager must be defined for filterModel to be set
114
139
  attributeManager.attributes.filterValues.needsUpdate() ||
140
+ attributeManager.attributes.filterCategoryValues?.needsUpdate() ||
115
141
  props.filterEnabled !== oldProps.filterEnabled ||
116
142
  props.filterRange !== oldProps.filterRange ||
117
- props.filterSoftRange !== oldProps.filterSoftRange;
143
+ props.filterSoftRange !== oldProps.filterSoftRange ||
144
+ props.filterCategories !== oldProps.filterCategories;
118
145
  if (filterNeedsUpdate) {
119
146
  this.setState({ filterNeedsUpdate });
120
147
  }
121
148
  }
149
+ if (attributeManager?.attributes.filterCategoryValues) {
150
+ // Update bitmask if accessor or selected categories has changed
151
+ const categoryBitMaskNeedsUpdate = attributeManager.attributes.filterCategoryValues.needsUpdate() ||
152
+ !deepEqual(props.filterCategories, oldProps.filterCategories, 2);
153
+ if (categoryBitMaskNeedsUpdate) {
154
+ this.setState({ categoryBitMaskNeedsUpdate });
155
+ }
156
+ // Need to recreate category map if categorySize has changed
157
+ const resetCategories = changeFlags.dataChanged;
158
+ if (resetCategories) {
159
+ this.setState({
160
+ categoryMap: Array(categorySize)
161
+ .fill(0)
162
+ .map(() => ({}))
163
+ });
164
+ attributeManager.attributes.filterCategoryValues.setNeedsUpdate('categoryMap');
165
+ }
166
+ }
122
167
  }
123
168
  draw(params, extension) {
124
169
  const filterFBO = this.state.filterFBO;
125
170
  const filterModel = this.state.filterModel;
126
171
  const filterNeedsUpdate = this.state.filterNeedsUpdate;
172
+ const categoryBitMaskNeedsUpdate = this.state.categoryBitMaskNeedsUpdate;
127
173
  const { onFilteredItemsChange } = this.props;
174
+ if (categoryBitMaskNeedsUpdate) {
175
+ extension._updateCategoryBitMask.call(this, params, extension);
176
+ }
128
177
  if (filterNeedsUpdate && onFilteredItemsChange && filterModel) {
129
- const { attributes: { filterValues, filterIndices } } = this.getAttributeManager();
178
+ const { attributes: { filterValues, filterCategoryValues, filterIndices } } = this.getAttributeManager();
130
179
  filterModel.setVertexCount(this.getNumInstances());
131
180
  this.context.device.clearWebGL({ framebuffer: filterFBO, color: [0, 0, 0, 0] });
132
181
  filterModel.updateModuleSettings(params.moduleParameters);
133
182
  // @ts-expect-error filterValue and filterIndices should always have buffer value
134
183
  filterModel.setAttributes({
135
184
  ...filterValues.getValue(),
185
+ ...filterCategoryValues?.getValue(),
136
186
  ...filterIndices?.getValue()
137
187
  });
188
+ filterModel.setUniforms(params.uniforms);
138
189
  filterModel.device.withParametersWebGL({
139
190
  framebuffer: filterFBO,
140
191
  // ts-ignore 'readonly' cannot be assigned to the mutable type '[GLBlendEquation, GLBlendEquation]'
@@ -159,4 +210,43 @@ export default class DataFilterExtension extends LayerExtension {
159
210
  filterFBO?.destroy();
160
211
  filterModel?.destroy();
161
212
  }
213
+ /**
214
+ * Updates the bitmask used on the GPU to perform the filter based on the
215
+ * `filterCategories` prop. The mapping between categories and bit in the bitmask
216
+ * is performed by `_getCategoryKey()`
217
+ */
218
+ _updateCategoryBitMask(params, extension) {
219
+ const { categorySize } = extension.opts;
220
+ const { filterCategories } = this.props;
221
+ const categoryBitMask = new Uint32Array([0, 0, 0, 0]);
222
+ const categoryFilters = (categorySize === 1 ? [filterCategories] : filterCategories);
223
+ const maxCategories = categorySize === 1 ? 128 : categorySize === 2 ? 64 : 32;
224
+ for (let c = 0; c < categoryFilters.length; c++) {
225
+ const categoryFilter = categoryFilters[c];
226
+ for (const category of categoryFilter) {
227
+ const key = extension._getCategoryKey.call(this, category, c);
228
+ if (key < maxCategories) {
229
+ const channel = c * (maxCategories / 32) + Math.floor(key / 32);
230
+ categoryBitMask[channel] += Math.pow(2, key % 32); // 1 << key fails for key > 30
231
+ }
232
+ else {
233
+ log.warn(`Exceeded maximum number of categories (${maxCategories})`)();
234
+ }
235
+ }
236
+ }
237
+ /* eslint-disable-next-line camelcase */
238
+ params.uniforms.filter_categoryBitMask = categoryBitMask;
239
+ this.state.categoryBitMaskNeedsUpdate = false;
240
+ }
241
+ /**
242
+ * Returns an index of bit in the bitmask for a given category. If the category has
243
+ * not yet been assigned a bit, a new one is assigned.
244
+ */
245
+ _getCategoryKey(category, channel) {
246
+ const categoryMap = this.state.categoryMap[channel];
247
+ if (!(category in categoryMap)) {
248
+ categoryMap[category] = Object.keys(categoryMap).length;
249
+ }
250
+ return categoryMap[category];
251
+ }
162
252
  }
@@ -1,5 +1,5 @@
1
1
  import type { ShaderModule } from '@luma.gl/shadertools';
2
- import type { DataFilterExtensionProps } from './data-filter-extension';
2
+ import type { DataFilterExtensionProps } from "./data-filter-extension.js";
3
3
  type DataFilterModuleSettings = {
4
4
  extensions: any[];
5
5
  } & DataFilterExtensionProps;
@@ -1 +1 @@
1
- {"version":3,"file":"shader-module.d.ts","sourceRoot":"","sources":["../../src/data-filter/shader-module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,yBAAyB,CAAC;AAmEtE,KAAK,wBAAwB,GAAG;IAC9B,UAAU,EAAE,GAAG,EAAE,CAAC;CACnB,GAAG,wBAAwB,CAAC;AAiG7B,eAAO,MAAM,YAAY,EAAE,YAAY,CAAC,wBAAwB,CAM/D,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,YAAY,CAAC,wBAAwB,CAMjE,CAAC"}
1
+ {"version":3,"file":"shader-module.d.ts","sourceRoot":"","sources":["../../src/data-filter/shader-module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAC,wBAAwB,EAAC,mCAAgC;AA8FtE,KAAK,wBAAwB,GAAG;IAC9B,UAAU,EAAE,GAAG,EAAE,CAAC;CACnB,GAAG,wBAAwB,CAAC;AAkG7B,eAAO,MAAM,YAAY,EAAE,YAAY,CAAC,wBAAwB,CAM/D,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,YAAY,CAAC,wBAAwB,CAMjE,CAAC"}
@@ -9,13 +9,16 @@ uniform DATAFILTER_TYPE filter_max;
9
9
  uniform bool filter_useSoftMargin;
10
10
  uniform bool filter_enabled;
11
11
  uniform bool filter_transformSize;
12
+ uniform ivec4 filter_categoryBitMask;
12
13
 
13
14
  #ifdef NON_INSTANCED_MODEL
14
15
  #define DATAFILTER_ATTRIB filterValues
15
16
  #define DATAFILTER_ATTRIB_64LOW filterValues64Low
17
+ #define DATACATEGORY_ATTRIB filterCategoryValues
16
18
  #else
17
19
  #define DATAFILTER_ATTRIB instanceFilterValues
18
20
  #define DATAFILTER_ATTRIB_64LOW instanceFilterValues64Low
21
+ #define DATACATEGORY_ATTRIB instanceFilterCategoryValues
19
22
  #endif
20
23
 
21
24
  in DATAFILTER_TYPE DATAFILTER_ATTRIB;
@@ -25,6 +28,7 @@ in DATAFILTER_TYPE DATAFILTER_ATTRIB;
25
28
  uniform DATAFILTER_TYPE filter_min64High;
26
29
  uniform DATAFILTER_TYPE filter_max64High;
27
30
  #endif
31
+ in DATACATEGORY_TYPE DATACATEGORY_ATTRIB;
28
32
 
29
33
  out float dataFilter_value;
30
34
 
@@ -40,7 +44,7 @@ float dataFilter_reduceValue(vec3 value) {
40
44
  float dataFilter_reduceValue(vec4 value) {
41
45
  return min(min(value.x, value.y), min(value.z, value.w));
42
46
  }
43
- void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFromMax) {
47
+ void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFromMax, DATACATEGORY_TYPE category) {
44
48
  if (filter_enabled) {
45
49
  if (filter_useSoftMargin) {
46
50
  dataFilter_value = dataFilter_reduceValue(
@@ -52,6 +56,29 @@ void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFrom
52
56
  step(filter_min, valueFromMin) * step(valueFromMax, filter_max)
53
57
  );
54
58
  }
59
+
60
+ #if DATACATEGORY_CHANNELS == 1 // One 128-bit mask
61
+ int dataFilter_masks = filter_categoryBitMask[int(category / 32.0)];
62
+ #elif DATACATEGORY_CHANNELS == 2 // Two 64-bit masks
63
+ ivec2 dataFilter_masks = ivec2(
64
+ filter_categoryBitMask[int(category.x / 32.0)],
65
+ filter_categoryBitMask[int(category.y / 32.0) + 2]
66
+ );
67
+ #elif DATACATEGORY_CHANNELS == 3 // Three 32-bit masks
68
+ ivec3 dataFilter_masks = filter_categoryBitMask.xyz;
69
+ #else // Four 32-bit masks
70
+ ivec4 dataFilter_masks = filter_categoryBitMask;
71
+ #endif
72
+
73
+ // Shift mask and extract relevant bits
74
+ DATACATEGORY_TYPE dataFilter_bits = DATACATEGORY_TYPE(dataFilter_masks) / pow(DATACATEGORY_TYPE(2.0), mod(category, 32.0));
75
+ dataFilter_bits = mod(floor(dataFilter_bits), 2.0);
76
+
77
+ #if DATACATEGORY_CHANNELS == 1
78
+ if(dataFilter_bits == 0.0) dataFilter_value = 0.0;
79
+ #else
80
+ if(any(equal(dataFilter_bits, DATACATEGORY_TYPE(0.0)))) dataFilter_value = 0.0;
81
+ #endif
55
82
  } else {
56
83
  dataFilter_value = 1.0;
57
84
  }
@@ -120,10 +147,11 @@ const inject = {
120
147
  #ifdef DATAFILTER_DOUBLE
121
148
  dataFilter_setValue(
122
149
  DATAFILTER_ATTRIB - filter_min64High + DATAFILTER_ATTRIB_64LOW,
123
- DATAFILTER_ATTRIB - filter_max64High + DATAFILTER_ATTRIB_64LOW
150
+ DATAFILTER_ATTRIB - filter_max64High + DATAFILTER_ATTRIB_64LOW,
151
+ DATACATEGORY_ATTRIB
124
152
  );
125
153
  #else
126
- dataFilter_setValue(DATAFILTER_ATTRIB, DATAFILTER_ATTRIB);
154
+ dataFilter_setValue(DATAFILTER_ATTRIB, DATAFILTER_ATTRIB, DATACATEGORY_ATTRIB);
127
155
  #endif
128
156
  `,
129
157
  'vs:#main-end': `
package/dist/dist.dev.js CHANGED
@@ -916,13 +916,16 @@ uniform DATAFILTER_TYPE filter_max;
916
916
  uniform bool filter_useSoftMargin;
917
917
  uniform bool filter_enabled;
918
918
  uniform bool filter_transformSize;
919
+ uniform ivec4 filter_categoryBitMask;
919
920
 
920
921
  #ifdef NON_INSTANCED_MODEL
921
922
  #define DATAFILTER_ATTRIB filterValues
922
923
  #define DATAFILTER_ATTRIB_64LOW filterValues64Low
924
+ #define DATACATEGORY_ATTRIB filterCategoryValues
923
925
  #else
924
926
  #define DATAFILTER_ATTRIB instanceFilterValues
925
927
  #define DATAFILTER_ATTRIB_64LOW instanceFilterValues64Low
928
+ #define DATACATEGORY_ATTRIB instanceFilterCategoryValues
926
929
  #endif
927
930
 
928
931
  in DATAFILTER_TYPE DATAFILTER_ATTRIB;
@@ -932,6 +935,7 @@ in DATAFILTER_TYPE DATAFILTER_ATTRIB;
932
935
  uniform DATAFILTER_TYPE filter_min64High;
933
936
  uniform DATAFILTER_TYPE filter_max64High;
934
937
  #endif
938
+ in DATACATEGORY_TYPE DATACATEGORY_ATTRIB;
935
939
 
936
940
  out float dataFilter_value;
937
941
 
@@ -947,7 +951,7 @@ float dataFilter_reduceValue(vec3 value) {
947
951
  float dataFilter_reduceValue(vec4 value) {
948
952
  return min(min(value.x, value.y), min(value.z, value.w));
949
953
  }
950
- void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFromMax) {
954
+ void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFromMax, DATACATEGORY_TYPE category) {
951
955
  if (filter_enabled) {
952
956
  if (filter_useSoftMargin) {
953
957
  dataFilter_value = dataFilter_reduceValue(
@@ -959,6 +963,29 @@ void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFrom
959
963
  step(filter_min, valueFromMin) * step(valueFromMax, filter_max)
960
964
  );
961
965
  }
966
+
967
+ #if DATACATEGORY_CHANNELS == 1 // One 128-bit mask
968
+ int dataFilter_masks = filter_categoryBitMask[int(category / 32.0)];
969
+ #elif DATACATEGORY_CHANNELS == 2 // Two 64-bit masks
970
+ ivec2 dataFilter_masks = ivec2(
971
+ filter_categoryBitMask[int(category.x / 32.0)],
972
+ filter_categoryBitMask[int(category.y / 32.0) + 2]
973
+ );
974
+ #elif DATACATEGORY_CHANNELS == 3 // Three 32-bit masks
975
+ ivec3 dataFilter_masks = filter_categoryBitMask.xyz;
976
+ #else // Four 32-bit masks
977
+ ivec4 dataFilter_masks = filter_categoryBitMask;
978
+ #endif
979
+
980
+ // Shift mask and extract relevant bits
981
+ DATACATEGORY_TYPE dataFilter_bits = DATACATEGORY_TYPE(dataFilter_masks) / pow(DATACATEGORY_TYPE(2.0), mod(category, 32.0));
982
+ dataFilter_bits = mod(floor(dataFilter_bits), 2.0);
983
+
984
+ #if DATACATEGORY_CHANNELS == 1
985
+ if(dataFilter_bits == 0.0) dataFilter_value = 0.0;
986
+ #else
987
+ if(any(equal(dataFilter_bits, DATACATEGORY_TYPE(0.0)))) dataFilter_value = 0.0;
988
+ #endif
962
989
  } else {
963
990
  dataFilter_value = 1.0;
964
991
  }
@@ -1028,10 +1055,11 @@ in float dataFilter_value;
1028
1055
  #ifdef DATAFILTER_DOUBLE
1029
1056
  dataFilter_setValue(
1030
1057
  DATAFILTER_ATTRIB - filter_min64High + DATAFILTER_ATTRIB_64LOW,
1031
- DATAFILTER_ATTRIB - filter_max64High + DATAFILTER_ATTRIB_64LOW
1058
+ DATAFILTER_ATTRIB - filter_max64High + DATAFILTER_ATTRIB_64LOW,
1059
+ DATACATEGORY_ATTRIB
1032
1060
  );
1033
1061
  #else
1034
- dataFilter_setValue(DATAFILTER_ATTRIB, DATAFILTER_ATTRIB);
1062
+ dataFilter_setValue(DATAFILTER_ATTRIB, DATAFILTER_ATTRIB, DATACATEGORY_ATTRIB);
1035
1063
  #endif
1036
1064
  `,
1037
1065
  "vs:#main-end": `
@@ -1166,13 +1194,21 @@ void main() {
1166
1194
  // src/data-filter/data-filter-extension.ts
1167
1195
  var defaultProps2 = {
1168
1196
  getFilterValue: { type: "accessor", value: 0 },
1197
+ getFilterCategory: { type: "accessor", value: 0 },
1169
1198
  onFilteredItemsChange: { type: "function", value: null, optional: true },
1170
1199
  filterEnabled: true,
1171
1200
  filterRange: [-1, 1],
1172
1201
  filterSoftRange: null,
1202
+ filterCategories: [0],
1173
1203
  filterTransformSize: true,
1174
1204
  filterTransformColor: true
1175
1205
  };
1206
+ var defaultOptions = {
1207
+ categorySize: 1,
1208
+ filterSize: 1,
1209
+ fp64: false,
1210
+ countItems: false
1211
+ };
1176
1212
  var DATA_TYPE_FROM_SIZE = {
1177
1213
  1: "float",
1178
1214
  2: "vec2",
@@ -1180,21 +1216,16 @@ void main() {
1180
1216
  4: "vec4"
1181
1217
  };
1182
1218
  var DataFilterExtension = class extends import_core4.LayerExtension {
1183
- constructor({
1184
- filterSize = 1,
1185
- fp64: fp642 = false,
1186
- countItems = false
1187
- } = {}) {
1188
- if (!DATA_TYPE_FROM_SIZE[filterSize]) {
1189
- throw new Error("filterSize out of range");
1190
- }
1191
- super({ filterSize, fp64: fp642, countItems });
1219
+ constructor(opts) {
1220
+ super({ ...defaultOptions, ...opts });
1192
1221
  }
1193
1222
  getShaders(extension) {
1194
- const { filterSize, fp64: fp642 } = extension.opts;
1223
+ const { categorySize, filterSize, fp64: fp642 } = extension.opts;
1195
1224
  return {
1196
1225
  modules: [fp642 ? shaderModule64 : shaderModule],
1197
1226
  defines: {
1227
+ DATACATEGORY_TYPE: DATA_TYPE_FROM_SIZE[categorySize],
1228
+ DATACATEGORY_CHANNELS: categorySize,
1198
1229
  DATAFILTER_TYPE: DATA_TYPE_FROM_SIZE[filterSize],
1199
1230
  DATAFILTER_DOUBLE: Boolean(fp642)
1200
1231
  }
@@ -1202,11 +1233,12 @@ void main() {
1202
1233
  }
1203
1234
  initializeState(context, extension) {
1204
1235
  const attributeManager = this.getAttributeManager();
1236
+ const { categorySize, filterSize, fp64: fp642 } = extension.opts;
1205
1237
  if (attributeManager) {
1206
1238
  attributeManager.add({
1207
1239
  filterValues: {
1208
- size: extension.opts.filterSize,
1209
- type: extension.opts.fp64 ? GLEnum.DOUBLE : GLEnum.FLOAT,
1240
+ size: filterSize,
1241
+ type: fp642 ? GLEnum.DOUBLE : GLEnum.FLOAT,
1210
1242
  accessor: "getFilterValue",
1211
1243
  shaderAttributes: {
1212
1244
  filterValues: {
@@ -1216,6 +1248,20 @@ void main() {
1216
1248
  divisor: 1
1217
1249
  }
1218
1250
  }
1251
+ },
1252
+ filterCategoryValues: {
1253
+ size: categorySize,
1254
+ type: GLEnum.FLOAT,
1255
+ accessor: "getFilterCategory",
1256
+ transform: categorySize === 1 ? (d) => extension._getCategoryKey.call(this, d, 0) : (d) => d.map((x, i) => extension._getCategoryKey.call(this, x, i)),
1257
+ shaderAttributes: {
1258
+ filterCategoryValues: {
1259
+ divisor: 0
1260
+ },
1261
+ instanceFilterCategoryValues: {
1262
+ divisor: 1
1263
+ }
1264
+ }
1219
1265
  }
1220
1266
  });
1221
1267
  }
@@ -1251,31 +1297,51 @@ void main() {
1251
1297
  this.setState({ filterFBO, filterModel });
1252
1298
  }
1253
1299
  }
1254
- updateState({ props, oldProps }) {
1300
+ updateState({ props, oldProps, changeFlags }, extension) {
1301
+ const attributeManager = this.getAttributeManager();
1302
+ const { categorySize } = extension.opts;
1255
1303
  if (this.state.filterModel) {
1256
- const attributeManager = this.getAttributeManager();
1257
- const filterNeedsUpdate = attributeManager.attributes.filterValues.needsUpdate() || props.filterEnabled !== oldProps.filterEnabled || props.filterRange !== oldProps.filterRange || props.filterSoftRange !== oldProps.filterSoftRange;
1304
+ const filterNeedsUpdate = attributeManager.attributes.filterValues.needsUpdate() || attributeManager.attributes.filterCategoryValues?.needsUpdate() || props.filterEnabled !== oldProps.filterEnabled || props.filterRange !== oldProps.filterRange || props.filterSoftRange !== oldProps.filterSoftRange || props.filterCategories !== oldProps.filterCategories;
1258
1305
  if (filterNeedsUpdate) {
1259
1306
  this.setState({ filterNeedsUpdate });
1260
1307
  }
1261
1308
  }
1309
+ if (attributeManager?.attributes.filterCategoryValues) {
1310
+ const categoryBitMaskNeedsUpdate = attributeManager.attributes.filterCategoryValues.needsUpdate() || !(0, import_core4._deepEqual)(props.filterCategories, oldProps.filterCategories, 2);
1311
+ if (categoryBitMaskNeedsUpdate) {
1312
+ this.setState({ categoryBitMaskNeedsUpdate });
1313
+ }
1314
+ const resetCategories = changeFlags.dataChanged;
1315
+ if (resetCategories) {
1316
+ this.setState({
1317
+ categoryMap: Array(categorySize).fill(0).map(() => ({}))
1318
+ });
1319
+ attributeManager.attributes.filterCategoryValues.setNeedsUpdate("categoryMap");
1320
+ }
1321
+ }
1262
1322
  }
1263
1323
  draw(params, extension) {
1264
1324
  const filterFBO = this.state.filterFBO;
1265
1325
  const filterModel = this.state.filterModel;
1266
1326
  const filterNeedsUpdate = this.state.filterNeedsUpdate;
1327
+ const categoryBitMaskNeedsUpdate = this.state.categoryBitMaskNeedsUpdate;
1267
1328
  const { onFilteredItemsChange } = this.props;
1329
+ if (categoryBitMaskNeedsUpdate) {
1330
+ extension._updateCategoryBitMask.call(this, params, extension);
1331
+ }
1268
1332
  if (filterNeedsUpdate && onFilteredItemsChange && filterModel) {
1269
1333
  const {
1270
- attributes: { filterValues, filterIndices }
1334
+ attributes: { filterValues, filterCategoryValues, filterIndices }
1271
1335
  } = this.getAttributeManager();
1272
1336
  filterModel.setVertexCount(this.getNumInstances());
1273
1337
  this.context.device.clearWebGL({ framebuffer: filterFBO, color: [0, 0, 0, 0] });
1274
1338
  filterModel.updateModuleSettings(params.moduleParameters);
1275
1339
  filterModel.setAttributes({
1276
1340
  ...filterValues.getValue(),
1341
+ ...filterCategoryValues?.getValue(),
1277
1342
  ...filterIndices?.getValue()
1278
1343
  });
1344
+ filterModel.setUniforms(params.uniforms);
1279
1345
  filterModel.device.withParametersWebGL(
1280
1346
  {
1281
1347
  framebuffer: filterFBO,
@@ -1301,6 +1367,34 @@ void main() {
1301
1367
  filterFBO?.destroy();
1302
1368
  filterModel?.destroy();
1303
1369
  }
1370
+ _updateCategoryBitMask(params, extension) {
1371
+ const { categorySize } = extension.opts;
1372
+ const { filterCategories } = this.props;
1373
+ const categoryBitMask = new Uint32Array([0, 0, 0, 0]);
1374
+ const categoryFilters = categorySize === 1 ? [filterCategories] : filterCategories;
1375
+ const maxCategories = categorySize === 1 ? 128 : categorySize === 2 ? 64 : 32;
1376
+ for (let c = 0; c < categoryFilters.length; c++) {
1377
+ const categoryFilter = categoryFilters[c];
1378
+ for (const category of categoryFilter) {
1379
+ const key = extension._getCategoryKey.call(this, category, c);
1380
+ if (key < maxCategories) {
1381
+ const channel = c * (maxCategories / 32) + Math.floor(key / 32);
1382
+ categoryBitMask[channel] += Math.pow(2, key % 32);
1383
+ } else {
1384
+ import_core4.log.warn(`Exceeded maximum number of categories (${maxCategories})`)();
1385
+ }
1386
+ }
1387
+ }
1388
+ params.uniforms.filter_categoryBitMask = categoryBitMask;
1389
+ this.state.categoryBitMaskNeedsUpdate = false;
1390
+ }
1391
+ _getCategoryKey(category, channel) {
1392
+ const categoryMap = this.state.categoryMap[channel];
1393
+ if (!(category in categoryMap)) {
1394
+ categoryMap[category] = Object.keys(categoryMap).length;
1395
+ }
1396
+ return categoryMap[category];
1397
+ }
1304
1398
  };
1305
1399
  DataFilterExtension.defaultProps = defaultProps2;
1306
1400
  DataFilterExtension.extensionName = "DataFilterExtension";