@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.
- package/BREAKING_CHANGES.md +8 -0
- package/Common/Core/LookupTable.js +2 -1
- package/Rendering/Core/ColorTransferFunction.js +3 -1
- package/Rendering/Core/Mapper.d.ts +5 -40
- package/Rendering/Core/Mapper.js +285 -127
- package/Rendering/Core/Mapper2D.d.ts +0 -5
- package/Rendering/OpenGL/HardwareSelector.d.ts +17 -0
- package/Rendering/OpenGL/HardwareSelector.js +12 -4
- package/Rendering/OpenGL/PolyDataMapper.js +3 -2
- package/package.json +1 -1
package/BREAKING_CHANGES.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
package/Rendering/Core/Mapper.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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:
|
|
465
|
+
values: scalarsArray
|
|
307
466
|
});
|
|
308
|
-
model.
|
|
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
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
|
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
|
-
|
|
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 = (
|
|
635
|
-
model._openGLRenderWindow =
|
|
636
|
-
model._renderer =
|
|
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
|
}
|