@kitware/vtk.js 31.1.0 → 32.0.0
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/ImageArrayMapper.d.ts +7 -125
- package/Rendering/Core/ImageCPRMapper.d.ts +7 -125
- package/Rendering/Core/ImageMapper.d.ts +7 -125
- package/Rendering/Core/ImageResliceMapper.d.ts +7 -125
- package/Rendering/Core/Mapper/CoincidentTopologyHelper.d.ts +194 -0
- package/Rendering/Core/Mapper/CoincidentTopologyHelper.js +8 -8
- package/Rendering/Core/Mapper/Static.js +15 -9
- package/Rendering/Core/Mapper.d.ts +12 -154
- package/Rendering/Core/Mapper.js +283 -125
- package/Rendering/Core/Mapper2D.d.ts +0 -5
- package/Rendering/Core/VolumeMapper/Constants.d.ts +1 -0
- package/Rendering/Core/VolumeMapper/Constants.js +2 -1
- package/Rendering/OpenGL/ImageCPRMapper.js +3 -1
- package/Rendering/OpenGL/ImageMapper.js +7 -2
- package/Rendering/OpenGL/ImageResliceMapper.js +6 -1
- package/Rendering/OpenGL/PolyDataMapper.js +6 -1
- package/Rendering/OpenGL/VolumeMapper.js +14 -4
- package/Rendering/OpenGL/glsl/vtkVolumeFS.glsl.js +1 -1
- package/Widgets/Representations/CircleContextRepresentation.js +3 -1
- package/index.d.ts +1 -0
- package/macros2.js +28 -12
- package/package.json +1 -1
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();
|
|
@@ -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;
|
|
@@ -14,6 +14,8 @@ import vtkViewNode from '../SceneGraph/ViewNode.js';
|
|
|
14
14
|
import { v as vtkPolyDataVS } from './glsl/vtkPolyDataVS.glsl.js';
|
|
15
15
|
import { v as vtkPolyDataFS } from './glsl/vtkPolyDataFS.glsl.js';
|
|
16
16
|
import { registerOverride } from './ViewNodeFactory.js';
|
|
17
|
+
import '../Core/Mapper/CoincidentTopologyHelper.js';
|
|
18
|
+
import { Resolve } from '../Core/Mapper/Static.js';
|
|
17
19
|
|
|
18
20
|
const {
|
|
19
21
|
vtkErrorMacro
|
|
@@ -68,7 +70,7 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
68
70
|
}
|
|
69
71
|
};
|
|
70
72
|
publicAPI.getCoincidentParameters = (ren, actor) => {
|
|
71
|
-
if (model.renderable.getResolveCoincidentTopology()) {
|
|
73
|
+
if (model.renderable.getResolveCoincidentTopology() === Resolve.PolygonOffset) {
|
|
72
74
|
return model.renderable.getCoincidentTopologyPolygonOffsetParameters();
|
|
73
75
|
}
|
|
74
76
|
return null;
|
|
@@ -14,7 +14,9 @@ import { InterpolationType } from '../Core/ImageProperty/Constants.js';
|
|
|
14
14
|
import { v as vtkPolyDataVS } from './glsl/vtkPolyDataVS.glsl.js';
|
|
15
15
|
import { v as vtkPolyDataFS } from './glsl/vtkPolyDataFS.glsl.js';
|
|
16
16
|
import vtkReplacementShaderMapper from './ReplacementShaderMapper.js';
|
|
17
|
+
import '../Core/Mapper/CoincidentTopologyHelper.js';
|
|
17
18
|
import { registerOverride } from './ViewNodeFactory.js';
|
|
19
|
+
import { Resolve } from '../Core/Mapper/Static.js';
|
|
18
20
|
|
|
19
21
|
const {
|
|
20
22
|
vtkErrorMacro
|
|
@@ -103,7 +105,10 @@ function vtkOpenGLImageMapper(publicAPI, model) {
|
|
|
103
105
|
}
|
|
104
106
|
};
|
|
105
107
|
publicAPI.getCoincidentParameters = (ren, actor) => {
|
|
106
|
-
if (
|
|
108
|
+
if (
|
|
109
|
+
// backwards compat with code that (errorneously) set this to boolean
|
|
110
|
+
// eslint-disable-next-line eqeqeq
|
|
111
|
+
model.renderable.getResolveCoincidentTopology() == Resolve.PolygonOffset) {
|
|
107
112
|
return model.renderable.getCoincidentTopologyPolygonOffsetParameters();
|
|
108
113
|
}
|
|
109
114
|
return null;
|
|
@@ -202,7 +207,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
|
|
|
202
207
|
case 1:
|
|
203
208
|
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Impl', [...splitStringOnEnter(`
|
|
204
209
|
#ifdef vtkImageLabelOutlineOn
|
|
205
|
-
vec3 centerPosIS = fragCoordToIndexSpace(gl_FragCoord);
|
|
210
|
+
vec3 centerPosIS = fragCoordToIndexSpace(gl_FragCoord);
|
|
206
211
|
float centerValue = texture2D(texture1, centerPosIS.xy).r;
|
|
207
212
|
bool pixelOnBorder = false;
|
|
208
213
|
vec3 tColor = texture2D(colorTexture1, vec2(centerValue * cscale0 + cshift0, 0.5)).rgb;
|
|
@@ -20,6 +20,8 @@ import { InterpolationType } from '../Core/ImageProperty/Constants.js';
|
|
|
20
20
|
import { Representation } from '../Core/Property/Constants.js';
|
|
21
21
|
import { VtkDataTypes } from '../../Common/Core/DataArray/Constants.js';
|
|
22
22
|
import { registerOverride } from './ViewNodeFactory.js';
|
|
23
|
+
import '../Core/Mapper/CoincidentTopologyHelper.js';
|
|
24
|
+
import { Resolve } from '../Core/Mapper/Static.js';
|
|
23
25
|
|
|
24
26
|
const {
|
|
25
27
|
vtkErrorMacro
|
|
@@ -94,7 +96,10 @@ function vtkOpenGLImageResliceMapper(publicAPI, model) {
|
|
|
94
96
|
}
|
|
95
97
|
};
|
|
96
98
|
publicAPI.getCoincidentParameters = (ren, actor) => {
|
|
97
|
-
if (
|
|
99
|
+
if (
|
|
100
|
+
// backwards compat with code that (errorneously) set this to boolean
|
|
101
|
+
// eslint-disable-next-line eqeqeq
|
|
102
|
+
model.renderable.getResolveCoincidentTopology() == Resolve.PolygonOffset) {
|
|
98
103
|
return model.renderable.getCoincidentTopologyPolygonOffsetParameters();
|
|
99
104
|
}
|
|
100
105
|
return null;
|
|
@@ -14,6 +14,8 @@ import vtkReplacementShaderMapper from './ReplacementShaderMapper.js';
|
|
|
14
14
|
import { registerOverride } from './ViewNodeFactory.js';
|
|
15
15
|
import { PassTypes } from './HardwareSelector/Constants.js';
|
|
16
16
|
import vtkDataSet from '../../Common/DataModel/DataSet.js';
|
|
17
|
+
import '../Core/Mapper/CoincidentTopologyHelper.js';
|
|
18
|
+
import { Resolve } from '../Core/Mapper/Static.js';
|
|
17
19
|
|
|
18
20
|
const {
|
|
19
21
|
FieldAssociations
|
|
@@ -391,7 +393,10 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) {
|
|
|
391
393
|
offset: 0.0
|
|
392
394
|
};
|
|
393
395
|
const prop = actor.getProperty();
|
|
394
|
-
if (
|
|
396
|
+
if (
|
|
397
|
+
// backwards compat with code that (errorneously) set this to boolean
|
|
398
|
+
// eslint-disable-next-line eqeqeq
|
|
399
|
+
model.renderable.getResolveCoincidentTopology() == Resolve.PolygonOffset || prop.getEdgeVisibility() && prop.getRepresentation() === Representation.SURFACE) {
|
|
395
400
|
const primType = model.lastBoundBO.getPrimitiveType();
|
|
396
401
|
if (primType === primTypes.Points || prop.getRepresentation() === Representation.POINTS) {
|
|
397
402
|
cp = model.renderable.getCoincidentTopologyPointOffsetParameter();
|
|
@@ -175,10 +175,14 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
175
175
|
if (iType === InterpolationType.LINEAR) {
|
|
176
176
|
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TrilinearOn', '#define vtkTrilinearOn').result;
|
|
177
177
|
}
|
|
178
|
-
const vtkImageLabelOutline =
|
|
178
|
+
const vtkImageLabelOutline = publicAPI.isLabelmapOutlineRequired(actor);
|
|
179
179
|
if (vtkImageLabelOutline === true) {
|
|
180
180
|
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ImageLabelOutlineOn', '#define vtkImageLabelOutlineOn').result;
|
|
181
181
|
}
|
|
182
|
+
const LabelEdgeProjection = model.renderable.getBlendMode() === BlendMode.LABELMAP_EDGE_PROJECTION_BLEND;
|
|
183
|
+
if (LabelEdgeProjection) {
|
|
184
|
+
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LabelEdgeProjectionOn', '#define vtkLabelEdgeProjectionOn').result;
|
|
185
|
+
}
|
|
182
186
|
const numComp = model.scalarTexture.getComponents();
|
|
183
187
|
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::NumComponents', `#define vtkNumComponents ${numComp}`).result;
|
|
184
188
|
const useIndependentComps = publicAPI.useIndependentComponents(actorProps);
|
|
@@ -350,7 +354,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
350
354
|
iComps: actorProps.getIndependentComponents(),
|
|
351
355
|
colorMixPreset: actorProps.getColorMixPreset(),
|
|
352
356
|
interpolationType: actorProps.getInterpolationType(),
|
|
353
|
-
useLabelOutline:
|
|
357
|
+
useLabelOutline: publicAPI.isLabelmapOutlineRequired(actor),
|
|
354
358
|
numComp,
|
|
355
359
|
maxSamples,
|
|
356
360
|
useGradientOpacity: actorProps.getUseGradientOpacity(0),
|
|
@@ -512,6 +516,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
512
516
|
vec3.divide(vctoijk, vctoijk, vsize);
|
|
513
517
|
program.setUniform3f('vVCToIJK', vctoijk[0], vctoijk[1], vctoijk[2]);
|
|
514
518
|
program.setUniform3i('volumeDimensions', dims[0], dims[1], dims[2]);
|
|
519
|
+
program.setUniform3f('volumeSpacings', spc[0], spc[1], spc[2]);
|
|
515
520
|
if (!model._openGLRenderWindow.getWebgl2()) {
|
|
516
521
|
const volInfo = model.scalarTexture.getVolumeInfo();
|
|
517
522
|
program.setUniformf('texWidth', model.scalarTexture.getWidth());
|
|
@@ -562,7 +567,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
562
567
|
program.setUniform3f(`vPlaneNormal${i}`, normal[0], normal[1], normal[2]);
|
|
563
568
|
program.setUniformf(`vPlaneDistance${i}`, dist);
|
|
564
569
|
}
|
|
565
|
-
if (
|
|
570
|
+
if (publicAPI.isLabelmapOutlineRequired(actor)) {
|
|
566
571
|
const image = model.currentInput;
|
|
567
572
|
const worldToIndex = image.getWorldToIndex();
|
|
568
573
|
program.setUniformMatrix('vWCtoIDX', worldToIndex);
|
|
@@ -750,7 +755,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
750
755
|
program.setUniformf('goshift0', -goRange[0] * (gomax - gomin) / (goRange[1] - goRange[0]) + gomin);
|
|
751
756
|
}
|
|
752
757
|
}
|
|
753
|
-
const vtkImageLabelOutline =
|
|
758
|
+
const vtkImageLabelOutline = publicAPI.isLabelmapOutlineRequired(actor);
|
|
754
759
|
if (vtkImageLabelOutline === true) {
|
|
755
760
|
const labelOutlineOpacity = actor.getProperty().getLabelOutlineOpacity();
|
|
756
761
|
program.setUniformf('outlineOpacity', labelOutlineOpacity);
|
|
@@ -1252,6 +1257,11 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
1252
1257
|
model.labelOutlineThicknessTexture = lTex.oglObject;
|
|
1253
1258
|
}
|
|
1254
1259
|
};
|
|
1260
|
+
publicAPI.isLabelmapOutlineRequired = actor => {
|
|
1261
|
+
const prop = actor.getProperty();
|
|
1262
|
+
const renderable = model.renderable;
|
|
1263
|
+
return prop.getUseLabelOutline() || renderable.getBlendMode() === BlendMode.LABELMAP_EDGE_PROJECTION_BLEND;
|
|
1264
|
+
};
|
|
1255
1265
|
}
|
|
1256
1266
|
|
|
1257
1267
|
// ----------------------------------------------------------------------------
|