@kitware/vtk.js 31.2.0 → 32.0.1

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,3 +1,11 @@
1
+ ## From 31.x to 32
2
+
3
+ - **vtkMapper**: remove `mapScalarsToTexture` from the public API. The function becomes protected and its API changes. This shouldn't cause any issue in most cases.
4
+
5
+ ## From 30.x to 31
6
+
7
+ - **Picking**: write to depth buffer for translucent pass when picking. This change fixes a picking issue with multiple translucent actors overlaid on top of each other. The hardware selector would just register the propID of the last rendered prop. This was because we explicitly turn off writing to the depth buffer when rendering translucent props as OIT doesn't need it. This change enables writing to the depth buffer when picking in the translucent pass. Applications relying on picked propID would see a difference in the IDs returned by the hardware selector.
8
+
1
9
  ## From 29.x to 30
2
10
 
3
11
  - **ResliceCursorWidget.interactionEvent**: no longer pass an object of `{computeFocalPointOffset, canUpdateFocalPoint}` but simply the type of the handle that triggers the event (e.g. `InteractionMethodsName.RotateLine`). The removed values can easily be recomputed by the consumers of the event by checking the type of the handle. Regarding `computeFocalPointOffset`, it is no longer advised to compute focal point offset for each interaction, instead observing `startInteraction()` should be considered (see ResliceCursorWidget example).
@@ -60,7 +60,8 @@ function vtkLookupTable(publicAPI, model) {
60
60
  publicAPI.usingLogScale = () => false;
61
61
 
62
62
  //----------------------------------------------------------------------------
63
- publicAPI.getNumberOfAvailableColors = () => model.table.length / 4;
63
+ // Don't count special colors (min, max, NaN) as available colors
64
+ publicAPI.getNumberOfAvailableColors = () => model.table.length / 4 - 3;
64
65
 
65
66
  //----------------------------------------------------------------------------
66
67
  // Apply shift/scale to the scalar value v and return the index.
@@ -937,7 +937,9 @@ function vtkColorTransferFunction(publicAPI, model) {
937
937
  // has been called.
938
938
  return model.tableSize;
939
939
  }
940
- return 16777216; // 2^24
940
+ const nNodes = model.nodes?.length ?? 0;
941
+ // The minimum is 4094 colors so that it fills in the 4096 texels texture in `mapScalarsToTexture`
942
+ return Math.max(4094, nNodes);
941
943
  };
942
944
 
943
945
  //----------------------------------------------------------------------------
@@ -20,11 +20,6 @@ interface IAbstractScalars {
20
20
  scalars: Nullable<vtkDataArray>;
21
21
  }
22
22
 
23
- interface IScalarToTextureCoordinate {
24
- texCoordS: number;
25
- texCoordT: number;
26
- }
27
-
28
23
  export interface IMapperInitialValues extends IAbstractMapper3DInitialValues {
29
24
  static?: boolean;
30
25
  scalarVisibility?: boolean;
@@ -80,22 +75,6 @@ export interface vtkMapper
80
75
  */
81
76
  colorToValue(): void;
82
77
 
83
- /**
84
- *
85
- * @param input
86
- * @param component
87
- * @param range
88
- * @param tableNumberOfColors
89
- * @param useLogScale
90
- */
91
- createColorTextureCoordinates(
92
- input: vtkDataArray,
93
- component: number,
94
- range: any,
95
- tableNumberOfColors: number,
96
- useLogScale: boolean
97
- ): vtkDataArray;
98
-
99
78
  /**
100
79
  * Create default lookup table. Generally used to create one when
101
80
  * none is available with the scalar data.
@@ -258,6 +237,11 @@ export interface vtkMapper
258
237
  */
259
238
  getViewSpecificProperties(): object;
260
239
 
240
+ /**
241
+ * The number of mapped colors in range
242
+ */
243
+ getNumberOfColorsInRange(): number;
244
+
261
245
  /**
262
246
  * Map the scalars (if there are any scalars and ScalarVisibility is on)
263
247
  * through the lookup table, returning an unsigned char RGBA array. This is
@@ -273,25 +257,6 @@ export interface vtkMapper
273
257
  */
274
258
  mapScalars(input: any, alpha: number): void;
275
259
 
276
- /**
277
- *
278
- * @param scalars
279
- * @param {Number} alpha
280
- */
281
- mapScalarsToTexture(scalars: any, alpha: number): void;
282
-
283
- /**
284
- *
285
- * @param {Number} scalarValue
286
- * @param {Number} rangeMin
287
- * @param {Number} invRangeWidth
288
- */
289
- scalarToTextureCoordinate(
290
- scalarValue: number,
291
- rangeMin: number,
292
- invRangeWidth: number
293
- ): IScalarToTextureCoordinate;
294
-
295
260
  /**
296
261
  *
297
262
  * @param {Number} arrayAccessMode
@@ -35,6 +35,234 @@ function notImplemented(method) {
35
35
  return () => macro.vtkErrorMacro(`vtkMapper::${method} - NOT IMPLEMENTED`);
36
36
  }
37
37
 
38
+ /**
39
+ * Increase by one the 3D coordinates
40
+ * It will follow a zigzag pattern so that each coordinate is the neighbor of the next coordinate
41
+ * This enables interpolation between two texels without issues
42
+ * Note: texture coordinates can't be interpolated using this pattern
43
+ * @param {vec3} coordinates The 3D coordinates using integers for each coorinate
44
+ * @param {vec3} dimensions The 3D dimensions of the volume
45
+ */
46
+ function updateZigzaggingCoordinates(coordinates, dimensions) {
47
+ const directionX = coordinates[1] % 2 === 0 ? 1 : -1;
48
+ coordinates[0] += directionX;
49
+ if (coordinates[0] >= dimensions[0] || coordinates[0] < 0) {
50
+ const directionY = coordinates[2] % 2 === 0 ? 1 : -1;
51
+ coordinates[0] -= directionX;
52
+ coordinates[1] += directionY;
53
+ if (coordinates[1] >= dimensions[1] || coordinates[1] < 0) {
54
+ coordinates[1] -= directionY;
55
+ coordinates[2]++;
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Returns the index in the array representing the volume from a 3D coordinate
62
+ * @param {vec3} coordinates The 3D integer coordinates
63
+ * @param {vec3} dimensions The 3D dimensions of the volume
64
+ * @returns The index in a flat array representing the volume
65
+ */
66
+ function getIndexFromCoordinates(coordinates, dimensions) {
67
+ return coordinates[0] + dimensions[0] * (coordinates[1] + dimensions[1] * coordinates[2]);
68
+ }
69
+
70
+ /**
71
+ * Write texture coordinates for the given `texelIndexPosition` in `textureCoordinate`.
72
+ * The `texelIndexPosition` is a floating point number that represents the distance in index space
73
+ * from the center of the first texel to the final output position.
74
+ * The output is given in texture coordinates and not in index coordinates (this is done at the very end of the function)
75
+ * @param {vec3} textureCoordinate The output texture coordinates (to avoid allocating a new Array)
76
+ * @param {Number} texelIndexPosition The floating point distance from the center of the first texel, following a zigzag pattern
77
+ * @param {vec3} dimensions The 3D dimensions of the volume
78
+ */
79
+ function getZigZagTextureCoordinatesFromTexelPosition(textureCoordinate, texelIndexPosition, dimensions) {
80
+ // First compute the integer textureCoordinate
81
+ const intTexelIndex = Math.floor(texelIndexPosition);
82
+ const xCoordBeforeWrap = intTexelIndex % (2 * dimensions[0]);
83
+ let xDirection;
84
+ let xEndFlag;
85
+ if (xCoordBeforeWrap < dimensions[0]) {
86
+ textureCoordinate[0] = xCoordBeforeWrap;
87
+ xDirection = 1;
88
+ xEndFlag = textureCoordinate[0] === dimensions[0] - 1;
89
+ } else {
90
+ textureCoordinate[0] = 2 * dimensions[0] - 1 - xCoordBeforeWrap;
91
+ xDirection = -1;
92
+ xEndFlag = textureCoordinate[0] === 0;
93
+ }
94
+ const intRowIndex = Math.floor(intTexelIndex / dimensions[0]);
95
+ const yCoordBeforeWrap = intRowIndex % (2 * dimensions[1]);
96
+ let yDirection;
97
+ let yEndFlag;
98
+ if (yCoordBeforeWrap < dimensions[1]) {
99
+ textureCoordinate[1] = yCoordBeforeWrap;
100
+ yDirection = 1;
101
+ yEndFlag = textureCoordinate[1] === dimensions[1] - 1;
102
+ } else {
103
+ textureCoordinate[1] = 2 * dimensions[1] - 1 - yCoordBeforeWrap;
104
+ yDirection = -1;
105
+ yEndFlag = textureCoordinate[1] === 0;
106
+ }
107
+ textureCoordinate[2] = Math.floor(intRowIndex / dimensions[1]);
108
+
109
+ // Now add the remainder either in x, y or z
110
+ const remainder = texelIndexPosition - intTexelIndex;
111
+ if (xEndFlag) {
112
+ if (yEndFlag) {
113
+ textureCoordinate[2] += remainder;
114
+ } else {
115
+ textureCoordinate[1] += yDirection * remainder;
116
+ }
117
+ } else {
118
+ textureCoordinate[0] += xDirection * remainder;
119
+ }
120
+
121
+ // textureCoordinates are in index space, convert to texture space
122
+ textureCoordinate[0] = (textureCoordinate[0] + 0.5) / dimensions[0];
123
+ textureCoordinate[1] = (textureCoordinate[1] + 0.5) / dimensions[1];
124
+ textureCoordinate[2] = (textureCoordinate[2] + 0.5) / dimensions[2];
125
+ }
126
+
127
+ // Associate an input vtkDataArray to an object { stringHash, textureCoordinates }
128
+ // A single dataArray only caches one array of texture coordinates, so this cache is useless when
129
+ // the input data array is used with two different lookup tables (which is very unlikely)
130
+ const colorTextureCoordinatesCache = new WeakMap();
131
+ /**
132
+ * The minimum of the range is mapped to the center of the first texel excluding min texel (texel at index distance 1)
133
+ * The maximum of the range is mapped to the center of the last texel excluding max and NaN texels (texel at index distance numberOfColorsInRange)
134
+ * The result is cached, and is reused if the arguments are the same and the input doesn't change
135
+ * @param {vtkDataArray} input The input data array used for coloring
136
+ * @param {Number} component The component of the input data array that is used for coloring (-1 for magnitude of the vectors)
137
+ * @param {Range} range The range of the scalars
138
+ * @param {Number} numberOfColorsInRange The number of colors that are used in the range
139
+ * @param {vec3} dimensions The dimensions of the texture
140
+ * @param {boolean} useLogScale If log scale should be used to transform input scalars
141
+ * @param {boolean} useZigzagPattern If a zigzag pattern should be used. Otherwise 1 row for colors (including min and max) and 1 row for NaN are used.
142
+ * @returns A vtkDataArray containing the texture coordinates (2D or 3D)
143
+ */
144
+ function getOrCreateColorTextureCoordinates(input, component, range, numberOfColorsInRange, dimensions, useLogScale, useZigzagPattern) {
145
+ // Caching using the "arguments" special object (because it is a pure function)
146
+ const argStrings = new Array(arguments.length);
147
+ for (let argIndex = 0; argIndex < arguments.length; ++argIndex) {
148
+ // eslint-disable-next-line prefer-rest-params
149
+ const arg = arguments[argIndex];
150
+ argStrings[argIndex] = arg.getMTime?.() ?? arg;
151
+ }
152
+ const stringHash = argStrings.join('/');
153
+ const cachedResult = colorTextureCoordinatesCache.get(input);
154
+ if (cachedResult && cachedResult.stringHash === stringHash) {
155
+ return cachedResult.textureCoordinates;
156
+ }
157
+
158
+ // The range used for computing coordinates have to change
159
+ // slightly to accommodate the special above- and below-range
160
+ // colors that are the first and last texels, respectively.
161
+ const scalarTexelWidth = (range[1] - range[0]) / (numberOfColorsInRange - 1);
162
+ const [paddedRangeMin, paddedRangeMax] = [range[0] - scalarTexelWidth, range[1] + scalarTexelWidth];
163
+
164
+ // Use the center of the voxel
165
+ const textureSOrigin = paddedRangeMin - 0.5 * scalarTexelWidth;
166
+ const textureSCoeff = 1.0 / (paddedRangeMax - paddedRangeMin + scalarTexelWidth);
167
+
168
+ // Compute in index space first
169
+ const texelIndexOrigin = paddedRangeMin;
170
+ const texelIndexCoeff = (numberOfColorsInRange + 1) / (paddedRangeMax - paddedRangeMin);
171
+ const inputV = input.getData();
172
+ const numScalars = input.getNumberOfTuples();
173
+ const numComps = input.getNumberOfComponents();
174
+ const useMagnitude = component < 0 || component >= numComps;
175
+ const numberOfOutputComponents = dimensions[2] <= 1 ? 2 : 3;
176
+ const output = vtkDataArray.newInstance({
177
+ numberOfComponents: numberOfOutputComponents,
178
+ values: new Float32Array(numScalars * numberOfOutputComponents)
179
+ });
180
+ const outputV = output.getData();
181
+ const nanTextureCoordinate = [0, 0, 0];
182
+ // Distance of NaN from the beginning:
183
+ // min: 0, ...colorsInRange, max: numberOfColorsInRange + 1, NaN = numberOfColorsInRange + 2
184
+ getZigZagTextureCoordinatesFromTexelPosition(nanTextureCoordinate, numberOfColorsInRange + 2, dimensions);
185
+
186
+ // Set a texture coordinate in the output for each tuple in the input
187
+ let inputIdx = 0;
188
+ let outputIdx = 0;
189
+ const textureCoordinate = [0.5, 0.5, 0.5];
190
+ for (let scalarIdx = 0; scalarIdx < numScalars; ++scalarIdx) {
191
+ // Get scalar value from magnitude or a single component
192
+ let scalarValue;
193
+ if (useMagnitude) {
194
+ let sum = 0;
195
+ for (let compIdx = 0; compIdx < numComps; ++compIdx) {
196
+ const compValue = inputV[inputIdx + compIdx];
197
+ sum += compValue * compValue;
198
+ }
199
+ scalarValue = Math.sqrt(sum);
200
+ } else {
201
+ scalarValue = inputV[inputIdx + component];
202
+ }
203
+ inputIdx += numComps;
204
+
205
+ // Apply log scale if necessary
206
+ if (useLogScale) {
207
+ scalarValue = vtkLookupTable.applyLogScale(scalarValue, range, range);
208
+ }
209
+
210
+ // Convert to texture coordinates and update output
211
+ if (isNan(scalarValue)) {
212
+ // Last texels are NaN colors (there is at least one NaN color)
213
+ textureCoordinate[0] = nanTextureCoordinate[0];
214
+ textureCoordinate[1] = nanTextureCoordinate[1];
215
+ textureCoordinate[2] = nanTextureCoordinate[2];
216
+ } else if (useZigzagPattern) {
217
+ // Texel position is in [0, numberOfColorsInRange + 1]
218
+ let texelIndexPosition = (scalarValue - texelIndexOrigin) * texelIndexCoeff;
219
+ if (texelIndexPosition < 1) {
220
+ // Use min color when smaller than range
221
+ texelIndexPosition = 0;
222
+ } else if (texelIndexPosition > numberOfColorsInRange) {
223
+ // Use max color when greater than range
224
+ texelIndexPosition = numberOfColorsInRange + 1;
225
+ }
226
+
227
+ // Convert the texel position into texture coordinate following a zigzag pattern
228
+ getZigZagTextureCoordinatesFromTexelPosition(textureCoordinate, texelIndexPosition, dimensions);
229
+ } else {
230
+ // 0.0 in t coordinate means not NaN. So why am I setting it to 0.49?
231
+ // Because when you are mapping scalars and you have a NaN adjacent to
232
+ // anything else, the interpolation everywhere should be NaN. Thus, I
233
+ // want the NaN color everywhere except right on the non-NaN neighbors.
234
+ // To simulate this, I set the t coord for the real numbers close to
235
+ // the threshold so that the interpolation almost immediately looks up
236
+ // the NaN value.
237
+ textureCoordinate[1] = 0.49;
238
+
239
+ // Some implementations apparently don't handle relatively large
240
+ // numbers (compared to the range [0.0, 1.0]) very well. In fact,
241
+ // values above 1122.0f appear to cause texture wrap-around on
242
+ // some systems even when edge clamping is enabled. Why 1122.0f? I
243
+ // don't know. For safety, we'll clamp at +/- 1000. This will
244
+ // result in incorrect images when the texture value should be
245
+ // above or below 1000, but I don't have a better solution.
246
+ const textureS = (scalarValue - textureSOrigin) * textureSCoeff;
247
+ if (textureS > 1000.0) {
248
+ textureCoordinate[0] = 1000.0;
249
+ } else if (textureS < -1000.0) {
250
+ textureCoordinate[0] = -1000.0;
251
+ } else {
252
+ textureCoordinate[0] = textureS;
253
+ }
254
+ }
255
+ for (let i = 0; i < numberOfOutputComponents; ++i) {
256
+ outputV[outputIdx++] = textureCoordinate[i];
257
+ }
258
+ }
259
+ colorTextureCoordinatesCache.set(input, {
260
+ stringHash,
261
+ textureCoordinates: output
262
+ });
263
+ return output;
264
+ }
265
+
38
266
  // ----------------------------------------------------------------------------
39
267
  // vtkMapper methods
40
268
  // ----------------------------------------------------------------------------
@@ -85,7 +313,7 @@ function vtkMapper(publicAPI, model) {
85
313
  if (!input || !model.scalarVisibility) {
86
314
  return {
87
315
  scalars: null,
88
- cellFLag: false
316
+ cellFlag: false
89
317
  };
90
318
  }
91
319
  let scalars = null;
@@ -155,7 +383,7 @@ function vtkMapper(publicAPI, model) {
155
383
  // Cell data always uses vertex color.
156
384
  // Only point data can use both texture and vertex coloring.
157
385
  if (publicAPI.canUseTextureMapForColoring(scalars, cellFlag)) {
158
- publicAPI.mapScalarsToTexture(scalars, alpha);
386
+ model.mapScalarsToTexture(scalars, cellFlag, alpha);
159
387
  } else {
160
388
  model.colorCoordinates = null;
161
389
  model.colorTextureMap = null;
@@ -169,98 +397,8 @@ function vtkMapper(publicAPI, model) {
169
397
  model.colorBuildString = `${publicAPI.getMTime()}${scalars.getMTime()}${alpha}`;
170
398
  };
171
399
 
172
- //-----------------------------------------------------------------------------
173
- publicAPI.scalarToTextureCoordinate = (scalarValue,
174
- // Input scalar
175
- rangeMin,
176
- // range[0]
177
- invRangeWidth) => {
178
- // 1/(range[1]-range[0])
179
- let texCoordS = 0.5; // Scalar value is arbitrary when NaN
180
- let texCoordT = 1.0; // 1.0 in t coordinate means NaN
181
- if (!isNan(scalarValue)) {
182
- // 0.0 in t coordinate means not NaN. So why am I setting it to 0.49?
183
- // Because when you are mapping scalars and you have a NaN adjacent to
184
- // anything else, the interpolation everywhere should be NaN. Thus, I
185
- // want the NaN color everywhere except right on the non-NaN neighbors.
186
- // To simulate this, I set the t coord for the real numbers close to
187
- // the threshold so that the interpolation almost immediately looks up
188
- // the NaN value.
189
- texCoordT = 0.49;
190
- texCoordS = (scalarValue - rangeMin) * invRangeWidth;
191
-
192
- // Some implementations apparently don't handle relatively large
193
- // numbers (compared to the range [0.0, 1.0]) very well. In fact,
194
- // values above 1122.0f appear to cause texture wrap-around on
195
- // some systems even when edge clamping is enabled. Why 1122.0f? I
196
- // don't know. For safety, we'll clamp at +/- 1000. This will
197
- // result in incorrect images when the texture value should be
198
- // above or below 1000, but I don't have a better solution.
199
- if (texCoordS > 1000.0) {
200
- texCoordS = 1000.0;
201
- } else if (texCoordS < -1000.0) {
202
- texCoordS = -1000.0;
203
- }
204
- }
205
- return {
206
- texCoordS,
207
- texCoordT
208
- };
209
- };
210
-
211
- //-----------------------------------------------------------------------------
212
- publicAPI.createColorTextureCoordinates = (input, component, range, tableNumberOfColors, useLogScale) => {
213
- // We have to change the range used for computing texture
214
- // coordinates slightly to accommodate the special above- and
215
- // below-range colors that are the first and last texels,
216
- // respectively.
217
- const scalarTexelWidth = (range[1] - range[0]) / tableNumberOfColors;
218
- const paddedRange = [range[0] - scalarTexelWidth, range[1] + scalarTexelWidth];
219
- const invRangeWidth = 1.0 / (paddedRange[1] - paddedRange[0]);
220
- const inputV = input.getData();
221
- const numScalars = input.getNumberOfTuples();
222
- const numComps = input.getNumberOfComponents();
223
- const output = vtkDataArray.newInstance({
224
- numberOfComponents: 2,
225
- values: new Float32Array(numScalars * 2)
226
- });
227
- const outputV = output.getData();
228
- if (component < 0 || component >= numComps) {
229
- // Convert the magnitude of all components to texture coordinates
230
- let inputIdx = 0;
231
- let outputIdx = 0;
232
- for (let scalarIdx = 0; scalarIdx < numScalars; ++scalarIdx) {
233
- let sum = 0;
234
- for (let compIdx = 0; compIdx < numComps; ++compIdx) {
235
- sum += inputV[inputIdx] * inputV[inputIdx];
236
- inputIdx++;
237
- }
238
- let magnitude = Math.sqrt(sum);
239
- if (useLogScale) {
240
- magnitude = vtkLookupTable.applyLogScale(magnitude, range, range);
241
- }
242
- const outputs = publicAPI.scalarToTextureCoordinate(magnitude, paddedRange[0], invRangeWidth);
243
- outputV[outputIdx++] = outputs.texCoordS;
244
- outputV[outputIdx++] = outputs.texCoordT;
245
- }
246
- } else {
247
- // Convert one of the components to texture coordinates
248
- let inputIdx = component;
249
- let outputIdx = 0;
250
- for (let scalarIdx = 0; scalarIdx < numScalars; ++scalarIdx) {
251
- let inputValue = inputV[inputIdx];
252
- if (useLogScale) {
253
- inputValue = vtkLookupTable.applyLogScale(inputValue, range, range);
254
- }
255
- const outputs = publicAPI.scalarToTextureCoordinate(inputValue, paddedRange[0], invRangeWidth);
256
- outputV[outputIdx++] = outputs.texCoordS;
257
- outputV[outputIdx++] = outputs.texCoordT;
258
- inputIdx += numComps;
259
- }
260
- }
261
- return output;
262
- };
263
- publicAPI.mapScalarsToTexture = (scalars, alpha) => {
400
+ // Protected method
401
+ model.mapScalarsToTexture = (scalars, cellFlag, alpha) => {
264
402
  const range = model.lookupTable.getRange();
265
403
  const useLogScale = model.lookupTable.usingLogScale();
266
404
  if (useLogScale) {
@@ -284,28 +422,52 @@ function vtkMapper(publicAPI, model) {
284
422
  // Create a dummy ramp of scalars.
285
423
  // In the future, we could extend vtkScalarsToColors.
286
424
  model.lookupTable.build();
287
- const numberOfColorsInRange = Math.min(Math.max(model.lookupTable.getNumberOfAvailableColors(), 64), 4094);
288
- // Texel width is computed before min and max colors that are out of range
289
- const scalarTexelWidth = (range[1] - range[0]) / numberOfColorsInRange;
290
- // Add two colors for special min and max colors
291
- const totalNumberOfColors = numberOfColorsInRange + 2;
292
- const newArray = new Float64Array(totalNumberOfColors * 2);
293
- for (let i = 0; i < totalNumberOfColors; ++i) {
294
- // minus 0.5 to start at below range color
295
- newArray[i] = range[0] + (i - 0.5) * scalarTexelWidth;
296
- if (useLogScale) {
297
- newArray[i] = 10.0 ** newArray[i];
298
- }
425
+ const numberOfAvailableColors = model.lookupTable.getNumberOfAvailableColors();
426
+
427
+ // Maximum dimensions and number of colors in range
428
+ const maxTextureWidthForCells = 2048;
429
+ const maxColorsInRangeForCells = maxTextureWidthForCells ** 3 - 3; // 3D but keep a color for min, max and NaN
430
+ const maxTextureWidthForPoints = 4096;
431
+ const maxColorsInRangeForPoints = maxTextureWidthForPoints - 2; // 1D but keep a color for min and max (NaN is in a different row)
432
+ // Minimum number of colors in range (excluding special colors like minColor, maxColor and NaNColor)
433
+ const minColorsInRange = 2;
434
+ // Maximum number of colors, limited by the maximum possible texture size
435
+ const maxColorsInRange = cellFlag ? maxColorsInRangeForCells : maxColorsInRangeForPoints;
436
+ model.numberOfColorsInRange = Math.min(Math.max(numberOfAvailableColors, minColorsInRange), maxColorsInRange);
437
+ const numberOfColorsForCells = model.numberOfColorsInRange + 3; // Add min, max and NaN
438
+ const numberOfColorsInUpperRowForPoints = model.numberOfColorsInRange + 2; // Add min and max ; the lower row will be used for NaN color
439
+ const textureDimensions = cellFlag ? [Math.min(Math.ceil(numberOfColorsForCells / maxTextureWidthForCells ** 0), maxTextureWidthForCells), Math.min(Math.ceil(numberOfColorsForCells / maxTextureWidthForCells ** 1), maxTextureWidthForCells), Math.min(Math.ceil(numberOfColorsForCells / maxTextureWidthForCells ** 2), maxTextureWidthForCells)] : [numberOfColorsInUpperRowForPoints, 2, 1];
440
+ const textureSize = textureDimensions[0] * textureDimensions[1] * textureDimensions[2];
441
+ const scalarsArray = new Float64Array(textureSize);
442
+
443
+ // Colors for NaN by default
444
+ scalarsArray.fill(NaN);
445
+
446
+ // Colors in range
447
+ // Add 2 to also get color for min and max
448
+ const numberOfNonSpecialColors = model.numberOfColorsInRange;
449
+ const numberOfNonNaNColors = numberOfNonSpecialColors + 2;
450
+ const textureCoordinates = [0, 0, 0];
451
+ const rangeMin = range[0];
452
+ const rangeDifference = range[1] - range[0];
453
+ for (let i = 0; i < numberOfNonNaNColors; ++i) {
454
+ const scalarsArrayIndex = getIndexFromCoordinates(textureCoordinates, textureDimensions);
455
+
456
+ // Minus 1 start at min color
457
+ const scalarValue = rangeMin + rangeDifference * (i - 1) / (numberOfNonSpecialColors - 1);
458
+ scalarsArray[scalarsArrayIndex] = useLogScale ? 10.0 ** scalarValue : scalarValue;
459
+
460
+ // Colors are zigzagging to allow interpolation between two neighbor colors when coloring cells
461
+ updateZigzaggingCoordinates(textureCoordinates, textureDimensions);
299
462
  }
300
- // Dimension on NaN.
301
- newArray.fill(NaN, totalNumberOfColors);
302
- model.colorTextureMap = vtkImageData.newInstance();
303
- model.colorTextureMap.setExtent(0, totalNumberOfColors - 1, 0, 1, 0, 0);
304
- const tmp = vtkDataArray.newInstance({
463
+ const scalarsDataArray = vtkDataArray.newInstance({
305
464
  numberOfComponents: 1,
306
- values: newArray
465
+ values: scalarsArray
307
466
  });
308
- model.colorTextureMap.getPointData().setScalars(model.lookupTable.mapScalars(tmp, model.colorMode, 0));
467
+ const colorsDataArray = model.lookupTable.mapScalars(scalarsDataArray, model.colorMode, 0);
468
+ model.colorTextureMap = vtkImageData.newInstance();
469
+ model.colorTextureMap.setDimensions(textureDimensions);
470
+ model.colorTextureMap.getPointData().setScalars(colorsDataArray);
309
471
  model.lookupTable.setAlpha(origAlpha);
310
472
  }
311
473
 
@@ -313,17 +475,12 @@ function vtkMapper(publicAPI, model) {
313
475
  // scalars, it is not how the old MapScalars for vertex coloring works.
314
476
  const scalarComponent = model.lookupTable.getVectorMode() === VectorMode.MAGNITUDE && scalars.getNumberOfComponents() > 1 ? -1 : model.lookupTable.getVectorComponent();
315
477
 
316
- // Reverse the computation of numberOfColorsInRange that is used to compute model.colorTextureMap
317
- const textureMapTuples = model.colorTextureMap.getPointData().getScalars().getNumberOfTuples();
318
- const totalNumberOfColors = textureMapTuples / 2;
319
- const numberOfColorsInRange = totalNumberOfColors - 2;
320
-
321
- // Create new coordinates if necessary.
322
- const colorCoordinatesString = `${scalars.getMTime()}/${scalarComponent}/${range}/${numberOfColorsInRange}/${useLogScale}`;
323
- if (colorCoordinatesString !== model.colorCoordinatesString) {
324
- model.colorCoordinates = publicAPI.createColorTextureCoordinates(scalars, scalarComponent, range, numberOfColorsInRange, useLogScale);
325
- model.colorCoordinatesString = colorCoordinatesString;
326
- }
478
+ // Create new coordinates if necessary, this function uses cache if possible.
479
+ // A zigzag pattern can't be used with point data, as interpolation of texture coordinates will be wrong
480
+ // A zigzag pattern can be used with cell data, as there will be no texture coordinates interpolation
481
+ // The texture generated using a zigzag pattern in one dimension is the same as without zigzag
482
+ // Therefore, the same code can be used for texture generation of point/cell data but not for texture coordinates
483
+ model.colorCoordinates = getOrCreateColorTextureCoordinates(scalars, scalarComponent, range, model.numberOfColorsInRange, model.colorTextureMap.getDimensions(), useLogScale, cellFlag);
327
484
  };
328
485
  publicAPI.getIsOpaque = () => {
329
486
  const input = publicAPI.getInputData();
@@ -438,11 +595,11 @@ function vtkMapper(publicAPI, model) {
438
595
  let inValue = 0;
439
596
  inValue += rawHighData[pos];
440
597
  inValue *= 256;
441
- inValue += rawLowData[pos];
598
+ inValue += rawLowData[pos + 2];
442
599
  inValue *= 256;
443
600
  inValue += rawLowData[pos + 1];
444
601
  inValue *= 256;
445
- inValue += rawLowData[pos + 2];
602
+ inValue += rawLowData[pos];
446
603
  const outValue = idMap[inValue];
447
604
  const highData = selector.getPixelBuffer(PassTypes.ID_HIGH24);
448
605
  highData[pos] = (outValue & 0xff000000) >> 24;
@@ -478,6 +635,7 @@ const DEFAULT_VALUES = {
478
635
  interpolateScalarsBeforeMapping: false,
479
636
  colorCoordinates: null,
480
637
  colorTextureMap: null,
638
+ numberOfColorsInRange: 0,
481
639
  forceCompileOnly: 0,
482
640
  useInvertibleColors: false,
483
641
  invertibleScalars: null,
@@ -492,7 +650,7 @@ function extend(publicAPI, model) {
492
650
 
493
651
  // Inheritance
494
652
  vtkAbstractMapper3D.extend(publicAPI, model, initialValues);
495
- macro.get(publicAPI, model, ['areScalarsMappedFromCells', 'colorCoordinates', 'colorMapColors', 'colorTextureMap', 'selectionWebGLIdsToVTKIds']);
653
+ macro.get(publicAPI, model, ['areScalarsMappedFromCells', 'colorCoordinates', 'colorMapColors', 'colorTextureMap', 'numberOfColorsInRange', 'selectionWebGLIdsToVTKIds']);
496
654
  macro.setGet(publicAPI, model, ['colorByArrayName', 'arrayAccessMode', 'colorMode', 'fieldDataTupleId', 'interpolateScalarsBeforeMapping', 'lookupTable', 'populateSelectionSettings', 'renderTime', 'scalarMode', 'scalarVisibility', 'static', 'useLookupTableScalarRange', 'customShaderAttributes' // point data array names that will be transferred to the VBO
497
655
  ]);
498
656
 
@@ -34,11 +34,6 @@ interface IAbstractScalars {
34
34
  cellFlag: boolean;
35
35
  }
36
36
 
37
- interface IScalarToTextureCoordinate {
38
- texCoordS: number;
39
- texCoordT: number;
40
- }
41
-
42
37
  export interface IMapper2DInitialValues extends IAbstractMapperInitialValues {
43
38
  arrayAccessMode?: number;
44
39
  colorMode?: number;
@@ -331,6 +331,23 @@ export function extend(
331
331
  initialValues?: IOpenGLHardwareSelectorInitialValues
332
332
  ): void;
333
333
 
334
+ /**
335
+ * The WebGL implementation of the hardware selector renders the id of the VBO
336
+ * vertices (gl_VertexID) with an offset to a RGBA texture.
337
+ * Only the RGB channels of the RGBA texture are used. This means that a single
338
+ * render can only draw 24 bits of information. To reach 32 bits, the hardware
339
+ * selector renders a second pass which only writes in the R channel of the
340
+ * texture.
341
+ *
342
+ * Note:
343
+ * - With Webgl 2, it is now possible to render directly to a R32 texture and
344
+ * even render to multiple render targets.
345
+ * - The raw pixel buffers fetched from WebGL are processed in the core mapper
346
+ * (see `processSelectorPixelBuffers`) instead of the PolydataMapper.
347
+ * - The processing of the raw pixel buffers does not output an UInt32Array as
348
+ * we could expect, but one or two textures with the same layout as the RGBA
349
+ * textures as input.
350
+ */
334
351
  export const vtkOpenGLHardwareSelector: {
335
352
  newInstance: typeof newInstance;
336
353
  extend: typeof extend;
@@ -358,6 +358,7 @@ function vtkOpenGLHardwareSelector(publicAPI, model) {
358
358
  model._renderer.setBackground(0.0, 0.0, 0.0, 0.0);
359
359
  const rpasses = model._openGLRenderWindow.getRenderPasses();
360
360
  publicAPI.beginSelection();
361
+ const pixelBufferSavedPasses = [];
361
362
  for (model.currentPass = PassTypes.MIN_KNOWN_PASS; model.currentPass <= PassTypes.MAX_KNOWN_PASS; model.currentPass++) {
362
363
  if (publicAPI.passRequired(model.currentPass)) {
363
364
  publicAPI.preCapturePass(model.currentPass);
@@ -369,9 +370,16 @@ function vtkOpenGLHardwareSelector(publicAPI, model) {
369
370
  }
370
371
  publicAPI.postCapturePass(model.currentPass);
371
372
  publicAPI.savePixelBuffer(model.currentPass);
372
- publicAPI.processPixelBuffers();
373
+ pixelBufferSavedPasses.push(model.currentPass);
373
374
  }
374
375
  }
376
+
377
+ // Process pixel buffers
378
+ pixelBufferSavedPasses.forEach(pass => {
379
+ model.currentPass = pass;
380
+ publicAPI.processPixelBuffers();
381
+ });
382
+ model.currentPass = PassTypes.MAX_KNOWN_PASS;
375
383
  publicAPI.endSelection();
376
384
 
377
385
  // restore original background
@@ -631,9 +639,9 @@ function vtkOpenGLHardwareSelector(publicAPI, model) {
631
639
 
632
640
  //----------------------------------------------------------------------------
633
641
 
634
- publicAPI.attach = (w, r) => {
635
- model._openGLRenderWindow = w;
636
- model._renderer = r;
642
+ publicAPI.attach = (openGLRenderWindow, renderer) => {
643
+ model._openGLRenderWindow = openGLRenderWindow;
644
+ model._renderer = renderer;
637
645
  };
638
646
 
639
647
  // override
@@ -438,7 +438,7 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) {
438
438
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Impl', ' gl_FragData[0] = vec4(float(idx%256)/255.0, float((idx/256)%256)/255.0, float((idx/65536)%256)/255.0, 1.0);').result;
439
439
  break;
440
440
  case PassTypes.ID_HIGH24:
441
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Impl', ' gl_FragData[0] = vec4(float(idx)/255.0, 0.0, 0.0, 1.0);').result;
441
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Impl', ' gl_FragData[0] = vec4(float((idx/16777216)%256)/255.0, 0.0, 0.0, 1.0);').result;
442
442
  break;
443
443
  default:
444
444
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Dec', 'uniform vec3 mapperIndex;').result;
@@ -953,6 +953,8 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) {
953
953
  if (publicAPI.getNeedToRebuildBufferObjects(ren, actor)) {
954
954
  publicAPI.buildBufferObjects(ren, actor);
955
955
  }
956
+ // Always call this function as the selector can change
957
+ publicAPI.updateMaximumPointCellIds();
956
958
  };
957
959
  publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => {
958
960
  // first do a coarse check
@@ -1092,7 +1094,6 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) {
1092
1094
  }
1093
1095
  if (model.renderable.getPopulateSelectionSettings()) {
1094
1096
  model.renderable.setSelectionWebGLIdsToVTKIds(model.selectionWebGLIdsToVTKIds);
1095
- publicAPI.updateMaximumPointCellIds();
1096
1097
  }
1097
1098
  model.VBOBuildString = toString;
1098
1099
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "31.2.0",
3
+ "version": "32.0.1",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",