@kitware/vtk.js 34.3.1 → 34.5.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.
@@ -0,0 +1,154 @@
1
+ import { vtkAlgorithm, vtkObject } from './../../interfaces';
2
+ import { Vector3 } from './../../types';
3
+
4
+ /**
5
+ *
6
+ */
7
+ export interface IDiskSourceInitialValues {
8
+ innerRadius?: number;
9
+ outerRadius?: number;
10
+ center?: Vector3;
11
+ normal?: Vector3;
12
+ radialResolution?: number;
13
+ circumferentialResolution?: number;
14
+ pointType?: string;
15
+ }
16
+
17
+ type vtkDiskSourceBase = vtkObject &
18
+ Omit<
19
+ vtkAlgorithm,
20
+ | 'getInputData'
21
+ | 'setInputData'
22
+ | 'setInputConnection'
23
+ | 'getInputConnection'
24
+ | 'addInputConnection'
25
+ | 'addInputData'
26
+ >;
27
+
28
+ export interface vtkDiskSource extends vtkDiskSourceBase {
29
+ /**
30
+ * Get the center of the disk.
31
+ */
32
+ getCenter(): Vector3;
33
+
34
+ /**
35
+ * Get the circumferential resolution of the disk.
36
+ */
37
+ getCircumferentialResolution(): number;
38
+
39
+ /**
40
+ * Get the inner radius of the disk.
41
+ */
42
+ getInnerRadius(): number;
43
+
44
+ /**
45
+ * Get the normal of the disk.
46
+ */
47
+ getNormal(): Vector3;
48
+
49
+ /**
50
+ * Get the outer radius of the disk.
51
+ */
52
+ getOuterRadius(): number;
53
+
54
+ /**
55
+ * Get the point type used for the disk.
56
+ */
57
+ getPointType(): string;
58
+
59
+ /**
60
+ * Get the radial resolution of the disk.
61
+ */
62
+ getRadialResolution(): number;
63
+
64
+ /**
65
+ * Expose methods
66
+ * @param inData
67
+ * @param outData
68
+ */
69
+ requestData(inData: any, outData: any): void;
70
+
71
+ /**
72
+ * Set the center of the disk.
73
+ * @param {Vector3} center
74
+ */
75
+ setCenter(center: Vector3): boolean;
76
+
77
+ /**
78
+ * Set the circumferential resolution of the disk.
79
+ * @param {number} resolution
80
+ */
81
+ setCircumferentialResolution(resolution: number): boolean;
82
+
83
+ /**
84
+ * Set the inner radius of the disk.
85
+ * @param {number} radius
86
+ */
87
+ setInnerRadius(radius: number): boolean;
88
+
89
+ /**
90
+ * Set the normal of the disk.
91
+ * @param {Vector3} normal
92
+ */
93
+ setNormal(normal: Vector3): boolean;
94
+
95
+ /**
96
+ * Set the outer radius of the disk.
97
+ * @param {number} radius
98
+ */
99
+ setOuterRadius(radius: number): boolean;
100
+
101
+ /**
102
+ * Set the point type used for the disk.
103
+ * @param {string} type
104
+ */
105
+ setPointType(type: string): boolean;
106
+
107
+ /**
108
+ * Set the radial resolution of the disk.
109
+ * @param {number} resolution
110
+ */
111
+ setRadialResolution(resolution: number): boolean;
112
+ }
113
+
114
+ /**
115
+ * Method used to decorate a given object (publicAPI+model) with vtkDiskSource characteristics.
116
+ *
117
+ * @param publicAPI object on which methods will be bounds (public)
118
+ * @param model object on which data structure will be bounds (protected)
119
+ * @param {IDiskSourceInitialValues} [initialValues] (default: {})
120
+ */
121
+ export function extend(
122
+ publicAPI: object,
123
+ model: object,
124
+ initialValues?: IDiskSourceInitialValues
125
+ ): void;
126
+
127
+ /**
128
+ * Method used to create a new instance of vtkDiskSource.
129
+ * @param {IDiskSourceInitialValues} [initialValues] for pre-setting some of its content
130
+ */
131
+ export function newInstance(
132
+ initialValues?: IDiskSourceInitialValues
133
+ ): vtkDiskSource;
134
+
135
+ /**
136
+ * vtkDiskSource creates a polygonal disk with a hole in the center. The disk
137
+ * has zero height. The user can specify the inner and outer radius of the disk,
138
+ * the radial and circumferential resolution of the polygonal representation,
139
+ * and the center and plane normal of the disk (i.e., the center and disk normal
140
+ * control the position and orientation of the disk).
141
+ *
142
+ * @example
143
+ * ```js
144
+ * import vtkDiskSource from '@kitware/vtk.js/Filters/Sources/DiskSource';
145
+ *
146
+ * const disk = vtkDiskSource.newInstance({ radius: 1, resolution: 80 });
147
+ * const polydata = disk.getOutputData();
148
+ * ```
149
+ */
150
+ export declare const vtkDiskSource: {
151
+ newInstance: typeof newInstance;
152
+ extend: typeof extend;
153
+ };
154
+ export default vtkDiskSource;
@@ -0,0 +1,137 @@
1
+ import { m as macro } from '../../macros2.js';
2
+ import vtkCellArray from '../../Common/Core/CellArray.js';
3
+ import vtkPolyData from '../../Common/DataModel/PolyData.js';
4
+ import vtkPoints from '../../Common/Core/Points.js';
5
+ import vtkMatrixBuilder from '../../Common/Core/MatrixBuilder.js';
6
+
7
+ const {
8
+ vtkErrorMacro
9
+ } = macro;
10
+
11
+ // ----------------------------------------------------------------------------
12
+ // vtkDiskSource methods
13
+ // ----------------------------------------------------------------------------
14
+
15
+ function vtkDiskSource(publicAPI, model) {
16
+ // Set our classname
17
+ model.classHierarchy.push('vtkDiskSource');
18
+ publicAPI.requestData = (inData, outData) => {
19
+ const {
20
+ innerRadius,
21
+ outerRadius,
22
+ center,
23
+ normal,
24
+ radialResolution,
25
+ circumferentialResolution,
26
+ pointType
27
+ } = model;
28
+ if (model.deleted) {
29
+ return;
30
+ }
31
+ let dataset = outData[0];
32
+
33
+ // Points
34
+ const points = vtkPoints.newInstance({
35
+ dataType: pointType
36
+ });
37
+ const n = [normal[0], normal[1], normal[2]];
38
+ const length = Math.hypot(n[0], n[1], n[2]);
39
+ if (length === 0) {
40
+ vtkErrorMacro('Normal vector cannot be zero-length');
41
+ return;
42
+ }
43
+ n[0] /= length;
44
+ n[1] /= length;
45
+ n[2] /= length;
46
+ const matrix = vtkMatrixBuilder.buildFromDegree().identity().translate(-center[0], -center[1], -center[2]).rotateFromDirections([0, 0, 1], n).translate(center[0], center[1], center[2]).getMatrix();
47
+ const polys = vtkCellArray.newInstance();
48
+
49
+ // Generate points
50
+ const deltaR = (outerRadius - innerRadius) / radialResolution;
51
+ const thetaStep = 2.0 * Math.PI / circumferentialResolution;
52
+ for (let i = 0; i < circumferentialResolution; i++) {
53
+ const theta = i * thetaStep;
54
+ const cosT = Math.cos(theta);
55
+ const sinT = Math.sin(theta);
56
+ for (let j = 0; j <= radialResolution; j++) {
57
+ const r = innerRadius + j * deltaR;
58
+ // point in XY plane before transform
59
+ const localPoint = [center[0] + r * cosT, center[1] + r * sinT, center[2]];
60
+ // apply matrix transform
61
+ const x = matrix[0] * localPoint[0] + matrix[4] * localPoint[1] + matrix[8] * localPoint[2] + matrix[12];
62
+ const y = matrix[1] * localPoint[0] + matrix[5] * localPoint[1] + matrix[9] * localPoint[2] + matrix[13];
63
+ const z = matrix[2] * localPoint[0] + matrix[6] * localPoint[1] + matrix[10] * localPoint[2] + matrix[14];
64
+ points.insertNextPoint(x, y, z);
65
+ }
66
+ }
67
+
68
+ // Generate cell connectivity (quads)
69
+ const cellCount = radialResolution * circumferentialResolution;
70
+ const cellData = new Uint8Array(cellCount * 5);
71
+ let offset = 0;
72
+ for (let i = 0; i < model.circumferentialResolution; i++) {
73
+ for (let j = 0; j < model.radialResolution; j++) {
74
+ const p0 = i * (model.radialResolution + 1) + j;
75
+ const p1 = p0 + 1;
76
+ const p2 = i < model.circumferentialResolution - 1 ? p1 + (model.radialResolution + 1) : j + 1; // wrap around
77
+ const p3 = p2 - 1;
78
+ cellData[offset++] = 4;
79
+ cellData[offset++] = p0;
80
+ cellData[offset++] = p1;
81
+ cellData[offset++] = p2;
82
+ cellData[offset++] = p3;
83
+ }
84
+ }
85
+ polys.setData(cellData, cellCount, 1);
86
+ dataset = vtkPolyData.newInstance();
87
+ dataset.setPoints(points);
88
+ dataset.setPolys(polys);
89
+
90
+ // Update output
91
+ outData[0] = dataset;
92
+ };
93
+ }
94
+
95
+ // ----------------------------------------------------------------------------
96
+ // Object factory
97
+ // ----------------------------------------------------------------------------
98
+
99
+ function defaultValues(initialValues) {
100
+ return {
101
+ innerRadius: 0.25,
102
+ outerRadius: 0.5,
103
+ center: [0, 0, 0],
104
+ normal: [0, 0, 1],
105
+ radialResolution: 1,
106
+ circumferentialResolution: 6,
107
+ pointType: 'Float64Array',
108
+ ...initialValues
109
+ };
110
+ }
111
+
112
+ // ----------------------------------------------------------------------------
113
+
114
+ function extend(publicAPI, model) {
115
+ let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
116
+ Object.assign(model, defaultValues(initialValues));
117
+
118
+ // Build VTK API
119
+ macro.obj(publicAPI, model);
120
+ macro.setGet(publicAPI, model, ['innerRadius', 'outerRadius', 'radialResolution', 'circumferentialResolution', 'pointType']);
121
+ macro.setGetArray(publicAPI, model, ['center', 'normal'], 3);
122
+ macro.algo(publicAPI, model, 0, 1);
123
+ vtkDiskSource(publicAPI, model);
124
+ }
125
+
126
+ // ----------------------------------------------------------------------------
127
+
128
+ const newInstance = macro.newInstance(extend, 'vtkDiskSource');
129
+
130
+ // ----------------------------------------------------------------------------
131
+
132
+ var vtkDiskSource$1 = {
133
+ newInstance,
134
+ extend
135
+ };
136
+
137
+ export { vtkDiskSource$1 as default, extend, newInstance };
@@ -5,6 +5,7 @@ import vtkConeSource from './Sources/ConeSource.js';
5
5
  import vtkCubeSource from './Sources/CubeSource.js';
6
6
  import vtkCursor3D from './Sources/Cursor3D.js';
7
7
  import vtkCylinderSource from './Sources/CylinderSource.js';
8
+ import vtkDiskSource from './Sources/DiskSource.js';
8
9
  import vtkImageGridSource from './Sources/ImageGridSource.js';
9
10
  import vtkLineSource from './Sources/LineSource.js';
10
11
  import vtkPlaneSource from './Sources/PlaneSource.js';
@@ -21,6 +22,7 @@ var Sources = {
21
22
  vtkCubeSource,
22
23
  vtkCursor3D,
23
24
  vtkCylinderSource,
25
+ vtkDiskSource,
24
26
  vtkImageGridSource,
25
27
  vtkLineSource,
26
28
  vtkPlaneSource,
@@ -14,6 +14,7 @@ import '../../Filters/Sources/ConeSource.js';
14
14
  import '../../Filters/Sources/CubeSource.js';
15
15
  import '../../Filters/Sources/Cursor3D.js';
16
16
  import '../../Filters/Sources/CylinderSource.js';
17
+ import '../../Filters/Sources/DiskSource.js';
17
18
  import '../../Filters/Sources/ImageGridSource.js';
18
19
  import '../../Filters/Sources/LineSource.js';
19
20
  import '../../Filters/Sources/PlaneSource.js';
@@ -106,15 +106,21 @@ export interface vtkImageProperty extends vtkObject {
106
106
  getUseLabelOutline(): boolean;
107
107
 
108
108
  /**
109
- * Set the 0 to 1 opacity of the label outline.
110
- * @param {Number} opacity
109
+ * Set opacity of the label outline.
110
+ *
111
+ * Opacity must be between 0 and 1.
112
+ * If the given opacity is a number, the opacity will apply to all outline segments.
113
+ * If the given opacity is an array of numbers, each opacity value will apply to the
114
+ * label equal to the opacity value's index + 1. (This is the same behavior as setLabelOutlineThickness).
115
+ *
116
+ * @param {Number | Number[]} opacity
111
117
  */
112
- setLabelOutlineOpacity(opacity: number): boolean;
118
+ setLabelOutlineOpacity(opacity: number | number[]): boolean;
113
119
 
114
120
  /**
115
121
  * Get the 0 to 1 opacity of the label outline.
116
122
  */
117
- getLabelOutlineOpacity(): number;
123
+ getLabelOutlineOpacity(): number | number[];
118
124
 
119
125
  /**
120
126
  * gets the label outline thickness
@@ -1,6 +1,10 @@
1
1
  import { m as macro } from '../../macros2.js';
2
2
  import vtkAbstractMapper from './AbstractMapper.js';
3
+ import vtkDataArray from '../../Common/Core/DataArray.js';
4
+ import vtkImageData from '../../Common/DataModel/ImageData.js';
3
5
  import vtkLookupTable from '../../Common/Core/LookupTable.js';
6
+ import vtkScalarsToColors from '../../Common/Core/ScalarsToColors/Constants.js';
7
+ import { i as isNan } from '../../Common/Core/Math/index.js';
4
8
  import Constants from './Mapper/Constants.js';
5
9
 
6
10
  const {
@@ -8,6 +12,237 @@ const {
8
12
  ScalarMode,
9
13
  GetArray
10
14
  } = Constants;
15
+ const {
16
+ VectorMode
17
+ } = vtkScalarsToColors;
18
+ const {
19
+ VtkDataTypes
20
+ } = vtkDataArray;
21
+
22
+ /**
23
+ * Increase by one the 3D coordinates
24
+ * It will follow a zigzag pattern so that each coordinate is the neighbor of the next coordinate
25
+ * This enables interpolation between two texels without issues
26
+ * Note: texture coordinates can't be interpolated using this pattern
27
+ * @param {vec3} coordinates The 3D coordinates using integers for each coordinate
28
+ * @param {vec3} dimensions The 3D dimensions of the volume
29
+ */
30
+ function updateZigzaggingCoordinates(coordinates, dimensions) {
31
+ const directionX = coordinates[1] % 2 === 0 ? 1 : -1;
32
+ coordinates[0] += directionX;
33
+ if (coordinates[0] >= dimensions[0] || coordinates[0] < 0) {
34
+ const directionY = coordinates[2] % 2 === 0 ? 1 : -1;
35
+ coordinates[0] -= directionX;
36
+ coordinates[1] += directionY;
37
+ if (coordinates[1] >= dimensions[1] || coordinates[1] < 0) {
38
+ coordinates[1] -= directionY;
39
+ coordinates[2]++;
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Returns the index in the array representing the volume from a 3D coordinate
46
+ * @param {vec3} coordinates The 3D integer coordinates
47
+ * @param {vec3} dimensions The 3D dimensions of the volume
48
+ * @returns The index in a flat array representing the volume
49
+ */
50
+ function getIndexFromCoordinates(coordinates, dimensions) {
51
+ return coordinates[0] + dimensions[0] * (coordinates[1] + dimensions[1] * coordinates[2]);
52
+ }
53
+
54
+ /**
55
+ * Write texture coordinates for the given `texelIndexPosition` in `textureCoordinate`.
56
+ * The `texelIndexPosition` is a floating point number that represents the distance in index space
57
+ * from the center of the first texel to the final output position.
58
+ * The output is given in texture coordinates and not in index coordinates (this is done at the very end of the function)
59
+ * @param {vec3} textureCoordinate The output texture coordinates (to avoid allocating a new Array)
60
+ * @param {Number} texelIndexPosition The floating point distance from the center of the first texel, following a zigzag pattern
61
+ * @param {vec3} dimensions The 3D dimensions of the volume
62
+ */
63
+ function getZigZagTextureCoordinatesFromTexelPosition(textureCoordinate, texelIndexPosition, dimensions) {
64
+ // First compute the integer textureCoordinate
65
+ const intTexelIndex = Math.floor(texelIndexPosition);
66
+ const xCoordBeforeWrap = intTexelIndex % (2 * dimensions[0]);
67
+ let xDirection;
68
+ let xEndFlag;
69
+ if (xCoordBeforeWrap < dimensions[0]) {
70
+ textureCoordinate[0] = xCoordBeforeWrap;
71
+ xDirection = 1;
72
+ xEndFlag = textureCoordinate[0] === dimensions[0] - 1;
73
+ } else {
74
+ textureCoordinate[0] = 2 * dimensions[0] - 1 - xCoordBeforeWrap;
75
+ xDirection = -1;
76
+ xEndFlag = textureCoordinate[0] === 0;
77
+ }
78
+ const intRowIndex = Math.floor(intTexelIndex / dimensions[0]);
79
+ const yCoordBeforeWrap = intRowIndex % (2 * dimensions[1]);
80
+ let yDirection;
81
+ let yEndFlag;
82
+ if (yCoordBeforeWrap < dimensions[1]) {
83
+ textureCoordinate[1] = yCoordBeforeWrap;
84
+ yDirection = 1;
85
+ yEndFlag = textureCoordinate[1] === dimensions[1] - 1;
86
+ } else {
87
+ textureCoordinate[1] = 2 * dimensions[1] - 1 - yCoordBeforeWrap;
88
+ yDirection = -1;
89
+ yEndFlag = textureCoordinate[1] === 0;
90
+ }
91
+ textureCoordinate[2] = Math.floor(intRowIndex / dimensions[1]);
92
+
93
+ // Now add the remainder either in x, y or z
94
+ const remainder = texelIndexPosition - intTexelIndex;
95
+ if (xEndFlag) {
96
+ if (yEndFlag) {
97
+ textureCoordinate[2] += remainder;
98
+ } else {
99
+ textureCoordinate[1] += yDirection * remainder;
100
+ }
101
+ } else {
102
+ textureCoordinate[0] += xDirection * remainder;
103
+ }
104
+
105
+ // textureCoordinates are in index space, convert to texture space
106
+ textureCoordinate[0] = (textureCoordinate[0] + 0.5) / dimensions[0];
107
+ textureCoordinate[1] = (textureCoordinate[1] + 0.5) / dimensions[1];
108
+ textureCoordinate[2] = (textureCoordinate[2] + 0.5) / dimensions[2];
109
+ }
110
+ // Associate an input vtkDataArray to an object { stringHash, textureCoordinates }
111
+ // A single dataArray only caches one array of texture coordinates, so this cache is useless when
112
+ // the input data array is used with two different lookup tables (which is very unlikely)
113
+ const colorTextureCoordinatesCache = new WeakMap();
114
+ /**
115
+ * The minimum of the range is mapped to the center of the first texel excluding min texel (texel at index distance 1)
116
+ * The maximum of the range is mapped to the center of the last texel excluding max and NaN texels (texel at index distance numberOfColorsInRange)
117
+ * The result is cached, and is reused if the arguments are the same and the input doesn't change
118
+ * @param {vtkDataArray} input The input data array used for coloring
119
+ * @param {Number} component The component of the input data array that is used for coloring (-1 for magnitude of the vectors)
120
+ * @param {Range} range The range of the scalars
121
+ * @param {boolean} useLogScale Should the values be transformed to logarithmic scale. When true, the range must already be in logarithmic scale.
122
+ * @param {Number} numberOfColorsInRange The number of colors that are used in the range
123
+ * @param {vec3} dimensions The dimensions of the texture
124
+ * @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.
125
+ * @returns A vtkDataArray containing the texture coordinates (2D or 3D)
126
+ */
127
+ function getOrCreateColorTextureCoordinates(input, component, range, useLogScale, numberOfColorsInRange, dimensions, useZigzagPattern) {
128
+ // Caching using the "arguments" special object (because it is a pure function)
129
+ const argStrings = new Array(arguments.length);
130
+ for (let argIndex = 0; argIndex < arguments.length; ++argIndex) {
131
+ // eslint-disable-next-line prefer-rest-params
132
+ const arg = arguments[argIndex];
133
+ argStrings[argIndex] = arg.getMTime?.() ?? arg;
134
+ }
135
+ const stringHash = argStrings.join('/');
136
+ const cachedResult = colorTextureCoordinatesCache.get(input);
137
+ if (cachedResult && cachedResult.stringHash === stringHash) {
138
+ return cachedResult.textureCoordinates;
139
+ }
140
+
141
+ // The range used for computing coordinates have to change
142
+ // slightly to accommodate the special above- and below-range
143
+ // colors that are the first and last texels, respectively.
144
+ const scalarTexelWidth = (range[1] - range[0]) / (numberOfColorsInRange - 1);
145
+ const [paddedRangeMin, paddedRangeMax] = [range[0] - scalarTexelWidth, range[1] + scalarTexelWidth];
146
+
147
+ // Use the center of the voxel
148
+ const textureSOrigin = paddedRangeMin - 0.5 * scalarTexelWidth;
149
+ const textureSCoeff = 1.0 / (paddedRangeMax - paddedRangeMin + scalarTexelWidth);
150
+
151
+ // Compute in index space first
152
+ const texelIndexOrigin = paddedRangeMin;
153
+ const texelIndexCoeff = (numberOfColorsInRange + 1) / (paddedRangeMax - paddedRangeMin);
154
+ const inputV = input.getData();
155
+ const numScalars = input.getNumberOfTuples();
156
+ const numComps = input.getNumberOfComponents();
157
+ const useMagnitude = component < 0 || component >= numComps;
158
+ const numberOfOutputComponents = dimensions[2] <= 1 ? 2 : 3;
159
+ const output = vtkDataArray.newInstance({
160
+ numberOfComponents: numberOfOutputComponents,
161
+ values: new Float32Array(numScalars * numberOfOutputComponents)
162
+ });
163
+ const outputV = output.getData();
164
+ const nanTextureCoordinate = [0, 0, 0];
165
+ // Distance of NaN from the beginning:
166
+ // min: 0, ...colorsInRange, max: numberOfColorsInRange + 1, NaN = numberOfColorsInRange + 2
167
+ getZigZagTextureCoordinatesFromTexelPosition(nanTextureCoordinate, numberOfColorsInRange + 2, dimensions);
168
+
169
+ // Set a texture coordinate in the output for each tuple in the input
170
+ let inputIdx = 0;
171
+ let outputIdx = 0;
172
+ const textureCoordinate = [0.5, 0.5, 0.5];
173
+ for (let scalarIdx = 0; scalarIdx < numScalars; ++scalarIdx) {
174
+ // Get scalar value from magnitude or a single component
175
+ let scalarValue;
176
+ if (useMagnitude) {
177
+ let sum = 0;
178
+ for (let compIdx = 0; compIdx < numComps; ++compIdx) {
179
+ const compValue = inputV[inputIdx + compIdx];
180
+ sum += compValue * compValue;
181
+ }
182
+ scalarValue = Math.sqrt(sum);
183
+ } else {
184
+ scalarValue = inputV[inputIdx + component];
185
+ }
186
+ if (useLogScale) {
187
+ scalarValue = Math.log10(scalarValue);
188
+ }
189
+ inputIdx += numComps;
190
+
191
+ // Convert to texture coordinates and update output
192
+ if (isNan(scalarValue)) {
193
+ // Last texels are NaN colors (there is at least one NaN color)
194
+ textureCoordinate[0] = nanTextureCoordinate[0];
195
+ textureCoordinate[1] = nanTextureCoordinate[1];
196
+ textureCoordinate[2] = nanTextureCoordinate[2];
197
+ } else if (useZigzagPattern) {
198
+ // Texel position is in [0, numberOfColorsInRange + 1]
199
+ let texelIndexPosition = (scalarValue - texelIndexOrigin) * texelIndexCoeff;
200
+ if (texelIndexPosition < 1) {
201
+ // Use min color when smaller than range
202
+ texelIndexPosition = 0;
203
+ } else if (texelIndexPosition > numberOfColorsInRange) {
204
+ // Use max color when greater than range
205
+ texelIndexPosition = numberOfColorsInRange + 1;
206
+ }
207
+
208
+ // Convert the texel position into texture coordinate following a zigzag pattern
209
+ getZigZagTextureCoordinatesFromTexelPosition(textureCoordinate, texelIndexPosition, dimensions);
210
+ } else {
211
+ // 0.0 in t coordinate means not NaN. So why am I setting it to 0.49?
212
+ // Because when you are mapping scalars and you have a NaN adjacent to
213
+ // anything else, the interpolation everywhere should be NaN. Thus, I
214
+ // want the NaN color everywhere except right on the non-NaN neighbors.
215
+ // To simulate this, I set the t coord for the real numbers close to
216
+ // the threshold so that the interpolation almost immediately looks up
217
+ // the NaN value.
218
+ textureCoordinate[1] = 0.49;
219
+
220
+ // Some implementations apparently don't handle relatively large
221
+ // numbers (compared to the range [0.0, 1.0]) very well. In fact,
222
+ // values above 1122.0f appear to cause texture wrap-around on
223
+ // some systems even when edge clamping is enabled. Why 1122.0f? I
224
+ // don't know. For safety, we'll clamp at +/- 1000. This will
225
+ // result in incorrect images when the texture value should be
226
+ // above or below 1000, but I don't have a better solution.
227
+ const textureS = (scalarValue - textureSOrigin) * textureSCoeff;
228
+ if (textureS > 1000.0) {
229
+ textureCoordinate[0] = 1000.0;
230
+ } else if (textureS < -1000.0) {
231
+ textureCoordinate[0] = -1000.0;
232
+ } else {
233
+ textureCoordinate[0] = textureS;
234
+ }
235
+ }
236
+ for (let i = 0; i < numberOfOutputComponents; ++i) {
237
+ outputV[outputIdx++] = textureCoordinate[i];
238
+ }
239
+ }
240
+ colorTextureCoordinatesCache.set(input, {
241
+ stringHash,
242
+ textureCoordinates: output
243
+ });
244
+ return output;
245
+ }
11
246
 
12
247
  // ---------------------------------------------------------------------------
13
248
  // vtkMapper2D methods
@@ -35,7 +270,7 @@ function vtkMapper2D(publicAPI, model) {
35
270
  if (!input || !model.scalarVisibility) {
36
271
  return {
37
272
  scalars: null,
38
- cellFLag: false
273
+ cellFlag: false
39
274
  };
40
275
  }
41
276
  let scalars = null;
@@ -96,8 +331,14 @@ function vtkMapper2D(publicAPI, model) {
96
331
  return mt;
97
332
  };
98
333
  publicAPI.mapScalars = (input, alpha) => {
99
- const scalars = publicAPI.getAbstractScalars(input, model.scalarMode, model.arrayAccessMode, model.arrayId, model.colorByArrayName).scalars;
334
+ const {
335
+ scalars,
336
+ cellFlag
337
+ } = publicAPI.getAbstractScalars(input, model.scalarMode, model.arrayAccessMode, model.arrayId, model.colorByArrayName);
338
+ model.areScalarsMappedFromCells = cellFlag;
100
339
  if (!scalars) {
340
+ model.colorCoordinates = null;
341
+ model.colorTextureMap = null;
101
342
  model.colorMapColors = null;
102
343
  return;
103
344
  }
@@ -108,14 +349,124 @@ function vtkMapper2D(publicAPI, model) {
108
349
  if (!model.useLookupTableScalarRange) {
109
350
  publicAPI.getLookupTable().setRange(model.scalarRange[0], model.scalarRange[1]);
110
351
  }
111
- const lut = publicAPI.getLookupTable();
112
- if (lut) {
113
- // Ensure that the lookup table is built
114
- lut.build();
115
- model.colorMapColors = lut.mapScalars(scalars, model.colorMode, model.fieldDataTupleId);
352
+ if (publicAPI.canUseTextureMapForColoring(scalars, cellFlag)) {
353
+ model.mapScalarsToTexture(scalars, cellFlag, alpha);
354
+ } else {
355
+ model.colorCoordinates = null;
356
+ model.colorTextureMap = null;
357
+ const lut = publicAPI.getLookupTable();
358
+ if (lut) {
359
+ // Ensure that the lookup table is built
360
+ lut.build();
361
+ model.colorMapColors = lut.mapScalars(scalars, model.colorMode, model.fieldDataTupleId);
362
+ }
116
363
  }
117
364
  model.colorBuildString = `${publicAPI.getMTime()}${scalars.getMTime()}${alpha}`;
118
365
  };
366
+ publicAPI.canUseTextureMapForColoring = (scalars, cellFlag) => {
367
+ if (cellFlag && !(model.colorMode === ColorMode.DIRECT_SCALARS)) {
368
+ return true; // cell data always use textures.
369
+ }
370
+
371
+ // index color does not use textures
372
+ if (model.lookupTable && model.lookupTable.getIndexedLookup()) {
373
+ return false;
374
+ }
375
+ if (!scalars) {
376
+ // no scalars on this dataset, we don't care if texture is used at all.
377
+ return false;
378
+ }
379
+ if (model.colorMode === ColorMode.DEFAULT && scalars.getDataType() === VtkDataTypes.UNSIGNED_CHAR || model.colorMode === ColorMode.DIRECT_SCALARS) {
380
+ // Don't use texture if direct coloring using RGB unsigned chars is
381
+ // requested.
382
+ return false;
383
+ }
384
+ return true;
385
+ };
386
+
387
+ // Protected method
388
+ model.mapScalarsToTexture = (scalars, cellFlag, alpha) => {
389
+ const range = model.lookupTable.getRange();
390
+ const useLogScale = model.lookupTable.usingLogScale();
391
+ const origAlpha = model.lookupTable.getAlpha();
392
+ const scaledRange = useLogScale ? [Math.log10(range[0]), Math.log10(range[1])] : range;
393
+
394
+ // Get rid of vertex color array. Only texture or vertex coloring
395
+ // can be active at one time. The existence of the array is the
396
+ // signal to use that technique.
397
+ model.colorMapColors = null;
398
+
399
+ // If the lookup table has changed, then recreate the color texture map.
400
+ // Set a new lookup table changes this->MTime.
401
+ if (model.colorTextureMap == null || publicAPI.getMTime() > model.colorTextureMap.getMTime() || model.lookupTable.getMTime() > model.colorTextureMap.getMTime() || model.lookupTable.getAlpha() !== alpha) {
402
+ model.lookupTable.setAlpha(alpha);
403
+ model.colorTextureMap = null;
404
+
405
+ // Get the texture map from the lookup table.
406
+ // Create a dummy ramp of scalars.
407
+ // In the future, we could extend vtkScalarsToColors.
408
+ model.lookupTable.build();
409
+ const numberOfAvailableColors = model.lookupTable.getNumberOfAvailableColors();
410
+
411
+ // Maximum dimensions and number of colors in range
412
+ const maxTextureWidthForCells = 2048;
413
+ const maxColorsInRangeForCells = maxTextureWidthForCells ** 3 - 3; // 3D but keep a color for min, max and NaN
414
+ const maxTextureWidthForPoints = 4096;
415
+ const maxColorsInRangeForPoints = maxTextureWidthForPoints - 2; // 1D but keep a color for min and max (NaN is in a different row)
416
+ // Minimum number of colors in range (excluding special colors like minColor, maxColor and NaNColor)
417
+ const minColorsInRange = 2;
418
+ // Maximum number of colors, limited by the maximum possible texture size
419
+ const maxColorsInRange = cellFlag ? maxColorsInRangeForCells : maxColorsInRangeForPoints;
420
+ model.numberOfColorsInRange = Math.min(Math.max(numberOfAvailableColors, minColorsInRange), maxColorsInRange);
421
+ const numberOfColorsForCells = model.numberOfColorsInRange + 3; // Add min, max and NaN
422
+ const numberOfColorsInUpperRowForPoints = model.numberOfColorsInRange + 2; // Add min and max ; the lower row will be used for NaN color
423
+ 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];
424
+ const textureSize = textureDimensions[0] * textureDimensions[1] * textureDimensions[2];
425
+ const scalarsArray = new Float64Array(textureSize);
426
+
427
+ // Colors for NaN by default
428
+ scalarsArray.fill(NaN);
429
+
430
+ // Colors in range
431
+ // Add 2 to also get color for min and max
432
+ const numberOfNonSpecialColors = model.numberOfColorsInRange;
433
+ const numberOfNonNaNColors = numberOfNonSpecialColors + 2;
434
+ const textureCoordinates = [0, 0, 0];
435
+ const rangeMin = scaledRange[0];
436
+ const rangeDifference = scaledRange[1] - scaledRange[0];
437
+ for (let i = 0; i < numberOfNonNaNColors; ++i) {
438
+ const scalarsArrayIndex = getIndexFromCoordinates(textureCoordinates, textureDimensions);
439
+
440
+ // Minus 1 start at min color
441
+ const intermediateValue = rangeMin + rangeDifference * (i - 1) / (numberOfNonSpecialColors - 1);
442
+ const scalarValue = useLogScale ? 10.0 ** intermediateValue : intermediateValue;
443
+ scalarsArray[scalarsArrayIndex] = scalarValue;
444
+
445
+ // Colors are zigzagging to allow interpolation between two neighbor colors when coloring cells
446
+ updateZigzaggingCoordinates(textureCoordinates, textureDimensions);
447
+ }
448
+ const scalarsDataArray = vtkDataArray.newInstance({
449
+ numberOfComponents: 1,
450
+ values: scalarsArray
451
+ });
452
+ const colorsDataArray = model.lookupTable.mapScalars(scalarsDataArray, model.colorMode, 0);
453
+ model.colorTextureMap = vtkImageData.newInstance();
454
+ model.colorTextureMap.setDimensions(textureDimensions);
455
+ model.colorTextureMap.getPointData().setScalars(colorsDataArray);
456
+ model.lookupTable.setAlpha(origAlpha);
457
+ }
458
+
459
+ // Although I like the feature of applying magnitude to single component
460
+ // scalars, it is not how the old MapScalars for vertex coloring works.
461
+ const scalarComponent = model.lookupTable.getVectorMode() === VectorMode.MAGNITUDE && scalars.getNumberOfComponents() > 1 ? -1 : model.lookupTable.getVectorComponent();
462
+
463
+ // Create new coordinates if necessary, this function uses cache if possible.
464
+ // A zigzag pattern can't be used with point data, as interpolation of texture coordinates will be wrong
465
+ // A zigzag pattern can be used with cell data, as there will be no texture coordinates interpolation
466
+ // The texture generated using a zigzag pattern in one dimension is the same as without zigzag
467
+ // Therefore, the same code can be used for texture generation of point/cell data but not for texture coordinates
468
+ model.colorCoordinates = getOrCreateColorTextureCoordinates(scalars, scalarComponent, scaledRange, useLogScale, model.numberOfColorsInRange, model.colorTextureMap.getDimensions(), cellFlag);
469
+ };
119
470
  publicAPI.getPrimitiveCount = () => {
120
471
  const input = publicAPI.getInputData();
121
472
  const pcount = {
@@ -143,6 +494,9 @@ const DEFAULT_VALUES = {
143
494
  arrayAccessMode: 1,
144
495
  // By_NAME
145
496
 
497
+ colorMapColors: null,
498
+ // Same as this->Colors
499
+ areScalarsMappedFromCells: false,
146
500
  renderTime: 0,
147
501
  colorByArrayName: null,
148
502
  transformCoordinate: null,
@@ -157,7 +511,7 @@ function extend(publicAPI, model) {
157
511
 
158
512
  // Inheritance
159
513
  vtkAbstractMapper.extend(publicAPI, model, initialValues);
160
- macro.get(publicAPI, model, ['colorMapColors']);
514
+ macro.get(publicAPI, model, ['areScalarsMappedFromCells', 'colorCoordinates', 'colorTextureMap', 'colorMapColors']);
161
515
  macro.setGet(publicAPI, model, ['arrayAccessMode', 'colorByArrayName', 'colorMode', 'lookupTable', 'renderTime', 'scalarMode', 'scalarVisibility', 'static', 'transformCoordinate', 'useLookupTableScalarRange', 'viewSpecificProperties', 'customShaderAttributes' // point data array names that will be transferred to the VBO
162
516
  ]);
163
517
 
@@ -54,7 +54,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
54
54
  // The openGLTexture is not shared
55
55
  model.openGLTexture.releaseGraphicsResources(renderWindow);
56
56
  // All these other resources are shared
57
- [model._colorTransferFunc, model._pwFunc, model._labelOutlineThicknessArray].forEach(coreObject => renderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI));
57
+ [model._colorTransferFunc, model._pwFunc, model._labelOutlineThicknessArray, model._labelOutlineOpacity].forEach(coreObject => renderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI));
58
58
  }
59
59
  publicAPI.buildPass = prepass => {
60
60
  if (prepass) {
@@ -131,7 +131,14 @@ function vtkOpenGLImageMapper(publicAPI, model) {
131
131
  // color shift and scale
132
132
  'uniform float cshift0;', 'uniform float cscale0;',
133
133
  // pwf shift and scale
134
- 'uniform float pwfshift0;', 'uniform float pwfscale0;', 'uniform sampler2D texture1;', 'uniform sampler2D colorTexture1;', 'uniform sampler2D pwfTexture1;', 'uniform sampler2D labelOutlineTexture1;', 'uniform float opacity;', 'uniform float outlineOpacity;'];
134
+ 'uniform float pwfshift0;', 'uniform float pwfscale0;', 'uniform sampler2D texture1;', 'uniform sampler2D colorTexture1;', 'uniform sampler2D pwfTexture1;', 'uniform float opacity;'];
135
+ if (actor.getProperty().getUseLabelOutline()) {
136
+ tcoordDec = tcoordDec.concat([
137
+ // outline thickness
138
+ 'uniform sampler2D labelOutlineTexture1;',
139
+ // outline opacity
140
+ 'uniform sampler2D labelOutlineOpacityTexture1;']);
141
+ }
135
142
  if (iComps) {
136
143
  for (let comp = 1; comp < tNumComp; comp++) {
137
144
  tcoordDec = tcoordDec.concat([
@@ -167,7 +174,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
167
174
  // check for the outline thickness and opacity
168
175
  const vtkImageLabelOutline = actor.getProperty().getUseLabelOutline();
169
176
  if (vtkImageLabelOutline === true) {
170
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LabelOutline::Dec', ['uniform int outlineThickness;', 'uniform float vpWidth;', 'uniform float vpHeight;', 'uniform float vpOffsetX;', 'uniform float vpOffsetY;', 'uniform mat4 PCWCMatrix;', 'uniform mat4 vWCtoIDX;', 'uniform ivec3 imageDimensions;', 'uniform int sliceAxis;']).result;
177
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LabelOutline::Dec', ['uniform float vpWidth;', 'uniform float vpHeight;', 'uniform float vpOffsetX;', 'uniform float vpOffsetY;', 'uniform mat4 PCWCMatrix;', 'uniform mat4 vWCtoIDX;', 'uniform ivec3 imageDimensions;', 'uniform int sliceAxis;']).result;
171
178
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ImageLabelOutlineOn', '#define vtkImageLabelOutlineOn').result;
172
179
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LabelOutlineHelperFunction', ['#ifdef vtkImageLabelOutlineOn', 'vec3 fragCoordToIndexSpace(vec4 fragCoord) {', ' vec4 pcPos = vec4(', ' (fragCoord.x / vpWidth - vpOffsetX - 0.5) * 2.0,', ' (fragCoord.y / vpHeight - vpOffsetY - 0.5) * 2.0,', ' (fragCoord.z - 0.5) * 2.0,', ' 1.0);', '', ' vec4 worldCoord = PCWCMatrix * pcPos;', ' vec4 vertex = (worldCoord/worldCoord.w);', '', ' vec3 index = (vWCtoIDX * vertex).xyz;', '', ' // half voxel fix for labelmapOutline', ' return (index + vec3(0.5)) / vec3(imageDimensions);', '}', 'vec2 getSliceCoords(vec3 coord, int axis) {', ' if (axis == 0) return coord.yz;', ' if (axis == 1) return coord.xz;', ' if (axis == 2) return coord.xy;', '}', '#endif']).result;
173
180
  }
@@ -209,6 +216,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
209
216
  int segmentIndex = int(centerValue * 255.0);
210
217
  float textureCoordinate = float(segmentIndex - 1) / 1024.0;
211
218
  float textureValue = texture2D(labelOutlineTexture1, vec2(textureCoordinate, 0.5)).r;
219
+ float outlineOpacity = texture2D(labelOutlineOpacityTexture1, vec2(textureCoordinate, 0.5)).r;
212
220
  int actualThickness = int(textureValue * 255.0);
213
221
 
214
222
  if (segmentIndex == 0){
@@ -301,7 +309,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
301
309
  if (!model.currentRenderPass && model.lastRenderPassShaderReplacement || model.currentRenderPass && model.currentRenderPass.getShaderReplacement() !== model.lastRenderPassShaderReplacement) {
302
310
  needRebuild = true;
303
311
  }
304
- if (needRebuild || model.lastHaveSeenDepthRequest !== model.haveSeenDepthRequest || cellBO.getProgram()?.getHandle() === 0 || model.lastTextureComponents !== tNumComp || model.lastIndependentComponents !== iComp) {
312
+ if (needRebuild || model.lastHaveSeenDepthRequest !== model.haveSeenDepthRequest || cellBO.getProgram()?.getHandle() === 0 || cellBO.getShaderSourceTime().getMTime() < model.renderable.getMTime() || cellBO.getShaderSourceTime().getMTime() < model.currentInput.getMTime() || cellBO.getShaderSourceTime().getMTime() < actor.getProperty().getMTime() || model.lastTextureComponents !== tNumComp || model.lastIndependentComponents !== iComp) {
305
313
  model.lastHaveSeenDepthRequest = model.haveSeenDepthRequest;
306
314
  model.lastTextureComponents = tNumComp;
307
315
  model.lastIndependentComponents = iComp;
@@ -417,8 +425,12 @@ function vtkOpenGLImageMapper(publicAPI, model) {
417
425
  cellBO.getProgram().setUniformi('colorTexture1', texColorUnit);
418
426
  const texOpacityUnit = model.pwfTexture.getTextureUnit();
419
427
  cellBO.getProgram().setUniformi('pwfTexture1', texOpacityUnit);
420
- const outlineThicknessUnit = model.labelOutlineThicknessTexture.getTextureUnit();
421
- cellBO.getProgram().setUniformi('labelOutlineTexture1', outlineThicknessUnit);
428
+ if (actor.getProperty().getUseLabelOutline()) {
429
+ const outlineThicknessUnit = model.labelOutlineThicknessTexture.getTextureUnit();
430
+ cellBO.getProgram().setUniformi('labelOutlineTexture1', outlineThicknessUnit);
431
+ const texOutlineOpacityUnit = model.labelOutlineOpacityTexture.getTextureUnit();
432
+ cellBO.getProgram().setUniformi('labelOutlineOpacityTexture1', texOutlineOpacityUnit);
433
+ }
422
434
  if (model.renderable.getNumberOfClippingPlanes()) {
423
435
  // add all the clipping planes
424
436
  let numClipPlanes = model.renderable.getNumberOfClippingPlanes();
@@ -449,13 +461,6 @@ function vtkOpenGLImageMapper(publicAPI, model) {
449
461
  cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
450
462
  cellBO.getProgram().setUniform4fv('clipPlanes', planeEquations);
451
463
  }
452
-
453
- // outline thickness and opacity
454
- const vtkImageLabelOutline = actor.getProperty().getUseLabelOutline();
455
- if (vtkImageLabelOutline === true) {
456
- const outlineOpacity = actor.getProperty().getLabelOutlineOpacity();
457
- cellBO.getProgram().setUniformf('outlineOpacity', outlineOpacity);
458
- }
459
464
  };
460
465
  publicAPI.setCameraShaderParameters = (cellBO, ren, actor) => {
461
466
  const program = cellBO.getProgram();
@@ -516,7 +521,10 @@ function vtkOpenGLImageMapper(publicAPI, model) {
516
521
  // activate the texture
517
522
  model.openGLTexture.activate();
518
523
  model.colorTexture.activate();
519
- model.labelOutlineThicknessTexture.activate();
524
+ if (actor.getProperty().getUseLabelOutline()) {
525
+ model.labelOutlineThicknessTexture.activate();
526
+ model.labelOutlineOpacityTexture.activate();
527
+ }
520
528
  model.pwfTexture.activate();
521
529
 
522
530
  // draw polygons
@@ -528,7 +536,10 @@ function vtkOpenGLImageMapper(publicAPI, model) {
528
536
  }
529
537
  model.openGLTexture.deactivate();
530
538
  model.colorTexture.deactivate();
531
- model.labelOutlineThicknessTexture.deactivate();
539
+ if (actor.getProperty().getUseLabelOutline()) {
540
+ model.labelOutlineThicknessTexture.deactivate();
541
+ model.labelOutlineOpacityTexture.deactivate();
542
+ }
532
543
  model.pwfTexture.deactivate();
533
544
  };
534
545
  publicAPI.renderPieceFinish = (ren, actor) => {};
@@ -560,7 +571,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
560
571
  publicAPI.buildBufferObjects(ren, actor);
561
572
  }
562
573
  };
563
- publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => model.VBOBuildTime.getMTime() < publicAPI.getMTime() || model.VBOBuildTime.getMTime() < actor.getMTime() || model.VBOBuildTime.getMTime() < model.renderable.getMTime() || model.VBOBuildTime.getMTime() < actor.getProperty().getMTime() || model.VBOBuildTime.getMTime() < model.currentInput.getMTime() || !model.openGLTexture?.getHandle() || !model.colorTexture?.getHandle() || !model.labelOutlineThicknessTexture?.getHandle() || !model.pwfTexture?.getHandle();
574
+ publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => model.VBOBuildTime.getMTime() < publicAPI.getMTime() || model.VBOBuildTime.getMTime() < actor.getMTime() || model.VBOBuildTime.getMTime() < model.renderable.getMTime() || model.VBOBuildTime.getMTime() < actor.getProperty().getMTime() || model.VBOBuildTime.getMTime() < model.currentInput.getMTime() || !model.openGLTexture?.getHandle() || !model.colorTexture?.getHandle() || actor.getProperty().getUseLabelOutline() && (!model.labelOutlineThicknessTexture?.getHandle() || !model.labelOutlineOpacityTexture?.getHandle()) || !model.pwfTexture?.getHandle();
564
575
  publicAPI.buildBufferObjects = (ren, actor) => {
565
576
  const image = model.currentInput;
566
577
  if (!image) {
@@ -740,9 +751,11 @@ function vtkOpenGLImageMapper(publicAPI, model) {
740
751
  } else {
741
752
  model.pwfTexture = pwfTex.oglObject;
742
753
  }
743
-
744
- // Build outline thickness buffer
745
- publicAPI.updatelabelOutlineThicknessTexture(actor);
754
+ if (actor.getProperty().getUseLabelOutline()) {
755
+ // Build outline thickness + opacity buffers
756
+ publicAPI.updatelabelOutlineThicknessTexture(actor);
757
+ publicAPI.updateLabelOutlineOpacityTexture(actor);
758
+ }
746
759
 
747
760
  // Find what IJK axis and what direction to slice along
748
761
  const {
@@ -955,6 +968,63 @@ function vtkOpenGLImageMapper(publicAPI, model) {
955
968
  model.VBOBuildString = toString;
956
969
  }
957
970
  };
971
+ publicAPI.updateLabelOutlineOpacityTexture = image => {
972
+ let labelOutlineOpacity = image.getProperty().getLabelOutlineOpacity();
973
+
974
+ // when the labelOutlineOpacity is a number, we use _cachedLabelOutlineOpacityObj
975
+ // as a stable object reference for `[labelOutlineOpacity]`.
976
+ if (typeof labelOutlineOpacity === 'number') {
977
+ if (model._cachedLabelOutlineOpacityObj?.[0] === labelOutlineOpacity) {
978
+ labelOutlineOpacity = model._cachedLabelOutlineOpacityObj;
979
+ } else {
980
+ labelOutlineOpacity = [labelOutlineOpacity];
981
+ }
982
+ model._cachedLabelOutlineOpacityObj = labelOutlineOpacity;
983
+ }
984
+ const lTex = model._openGLRenderWindow.getGraphicsResourceForObject(labelOutlineOpacity);
985
+ const toString = `${labelOutlineOpacity.join('-')}`;
986
+ const reBuildL = !lTex?.oglObject?.getHandle() || lTex?.hash !== toString;
987
+ if (reBuildL) {
988
+ let lWidth = model.renderable.getLabelOutlineTextureWidth();
989
+ if (lWidth <= 0) {
990
+ lWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
991
+ }
992
+ const lHeight = 1;
993
+ const lSize = lWidth * lHeight;
994
+ const lTable = new Float32Array(lSize);
995
+ for (let i = 0; i < lWidth; ++i) {
996
+ // Retrieve the opacity value for the current segment index.
997
+ // If the value is undefined, use the first element's value as a default, otherwise use the value (even if 0)
998
+ lTable[i] = labelOutlineOpacity[i] ?? labelOutlineOpacity[0];
999
+ }
1000
+ model.labelOutlineOpacityTexture = vtkOpenGLTexture.newInstance({
1001
+ resizable: false
1002
+ });
1003
+ model.labelOutlineOpacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1004
+ model.labelOutlineOpacityTexture.resetFormatAndType();
1005
+ model.labelOutlineOpacityTexture.setMinificationFilter(Filter.NEAREST);
1006
+ model.labelOutlineOpacityTexture.setMagnificationFilter(Filter.NEAREST);
1007
+
1008
+ // Create a 2D texture (acting as 1D) from the raw data
1009
+ model.labelOutlineOpacityTexture.create2DFromRaw({
1010
+ width: lWidth,
1011
+ height: lHeight,
1012
+ numComps: 1,
1013
+ dataType: VtkDataTypes.FLOAT,
1014
+ data: lTable
1015
+ });
1016
+ if (labelOutlineOpacity) {
1017
+ model._openGLRenderWindow.setGraphicsResourceForObject(labelOutlineOpacity, model.labelOutlineOpacityTexture, toString);
1018
+ if (labelOutlineOpacity !== model._labelOutlineOpacity) {
1019
+ model._openGLRenderWindow.registerGraphicsResourceUser(labelOutlineOpacity, publicAPI);
1020
+ model._openGLRenderWindow.unregisterGraphicsResourceUser(model._labelOutlineOpacity, publicAPI);
1021
+ }
1022
+ model._labelOutlineOpacity = labelOutlineOpacity;
1023
+ }
1024
+ } else {
1025
+ model.labelOutlineOpacityTexture = lTex.oglObject;
1026
+ }
1027
+ };
958
1028
  publicAPI.updatelabelOutlineThicknessTexture = image => {
959
1029
  const labelOutlineThicknessArray = image.getProperty().getLabelOutlineThicknessByReference();
960
1030
  const lTex = model._openGLRenderWindow.getGraphicsResourceForObject(labelOutlineThicknessArray);
@@ -1046,7 +1116,7 @@ const DEFAULT_VALUES = {
1046
1116
  colorTexture: null,
1047
1117
  pwfTexture: null,
1048
1118
  labelOutlineThicknessTexture: null,
1049
- labelOutlineThicknessTextureString: null,
1119
+ labelOutlineOpacityTexture: null,
1050
1120
  lastHaveSeenDepthRequest: false,
1051
1121
  haveSeenDepthRequest: false,
1052
1122
  lastTextureComponents: 0
@@ -1,13 +1,13 @@
1
1
  import { mat4 } from 'gl-matrix';
2
2
  import { n as newInstance$1, e as setGet, o as obj, c as macro } from '../../macros2.js';
3
3
  import vtkHelper from './Helper.js';
4
- import vtkMapper2D from '../Core/Mapper2D.js';
5
4
  import vtkPoints from '../../Common/Core/Points.js';
6
5
  import { v as vtkPolyData2DFS } from './glsl/vtkPolyData2DFS.glsl.js';
7
6
  import { v as vtkPolyData2DVS } from './glsl/vtkPolyData2DVS.glsl.js';
8
7
  import vtkReplacementShaderMapper from './ReplacementShaderMapper.js';
9
8
  import vtkShaderProgram from './ShaderProgram.js';
10
9
  import vtkViewNode from '../SceneGraph/ViewNode.js';
10
+ import vtkOpenGLTexture from './Texture.js';
11
11
  import { P as round } from '../../Common/Core/Math/index.js';
12
12
  import { DisplayLocation } from '../Core/Property2D/Constants.js';
13
13
  import { registerOverride } from './ViewNodeFactory.js';
@@ -17,8 +17,9 @@ const {
17
17
  primTypes
18
18
  } = vtkHelper;
19
19
  const {
20
- ScalarMode
21
- } = vtkMapper2D;
20
+ Filter,
21
+ Wrap
22
+ } = vtkOpenGLTexture;
22
23
  const {
23
24
  vtkErrorMacro
24
25
  } = macro;
@@ -82,6 +83,11 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
82
83
  if (!model.currentInput.getPoints || !model.currentInput.getPoints().getNumberOfValues()) {
83
84
  return;
84
85
  }
86
+
87
+ // cull back face to avoid double drawing
88
+ const gl = model.context;
89
+ model._openGLRenderWindow.enableCullFace();
90
+ gl.cullFace(gl.BACK);
85
91
  publicAPI.renderPieceStart(ren, actor);
86
92
  publicAPI.renderPieceDraw(ren, actor);
87
93
  publicAPI.renderPieceFinish(ren, actor);
@@ -94,6 +100,13 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
94
100
  model._openGLRenderer.getSelector().renderProp(actor);
95
101
  }
96
102
  }
103
+
104
+ // If we are coloring by texture, then load the texture map.
105
+ // Use Map as indicator, because texture hangs around.
106
+ if (model.renderable.getColorTextureMap()) {
107
+ model.internalColorTexture.activate();
108
+ }
109
+
97
110
  // make sure the BOs are up to date
98
111
  publicAPI.updateBufferObjects(ren, actor);
99
112
 
@@ -133,19 +146,44 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
133
146
  }
134
147
  model.renderable.mapScalars(poly, actor.getProperty().getOpacity());
135
148
  const c = model.renderable.getColorMapColors();
136
- model.haveCellScalars = false;
137
- const scalarMode = model.renderable.getScalarMode();
138
- if (model.renderable.getScalarVisibility()) {
139
- // We must figure out how the scalars should be mapped to the polydata.
140
- if ((scalarMode === ScalarMode.USE_CELL_DATA || scalarMode === ScalarMode.USE_CELL_FIELD_DATA || scalarMode === ScalarMode.USE_FIELD_DATA || !poly.getPointData().getScalars()) && scalarMode !== ScalarMode.USE_POINT_FIELD_DATA && c) {
141
- model.haveCellScalars = true;
142
- }
143
- }
144
149
  const representation = actor.getProperty().getRepresentation();
145
150
  let tcoords = poly.getPointData().getTCoords();
146
151
  if (!model.openGLActor2D.getActiveTextures()) {
147
152
  tcoords = null;
148
153
  }
154
+
155
+ // Flag to check if tcoords are per cell instead of per point
156
+ let useTCoordsPerCell = false;
157
+ // handle color mapping via texture
158
+ if (model.renderable.getColorCoordinates()) {
159
+ tcoords = model.renderable.getColorCoordinates();
160
+ useTCoordsPerCell = model.renderable.getAreScalarsMappedFromCells();
161
+ if (!model.internalColorTexture) {
162
+ model.internalColorTexture = vtkOpenGLTexture.newInstance({
163
+ resizable: true
164
+ });
165
+ }
166
+ const tex = model.internalColorTexture;
167
+ // the following 4 lines allow for NPOT textures
168
+ tex.setMinificationFilter(Filter.NEAREST);
169
+ tex.setMagnificationFilter(Filter.NEAREST);
170
+ tex.setWrapS(Wrap.CLAMP_TO_EDGE);
171
+ tex.setWrapT(Wrap.CLAMP_TO_EDGE);
172
+ tex.setOpenGLRenderWindow(model._openGLRenderWindow);
173
+ const input = model.renderable.getColorTextureMap();
174
+ const ext = input.getExtent();
175
+ const inScalars = input.getPointData().getScalars();
176
+ tex.create2DFromRaw({
177
+ width: ext[1] - ext[0] + 1,
178
+ height: ext[3] - ext[2] + 1,
179
+ numComps: inScalars.getNumberOfComponents(),
180
+ dataType: inScalars.getDataType(),
181
+ data: inScalars.getData()
182
+ });
183
+ tex.activate();
184
+ tex.sendParameters();
185
+ tex.deactivate();
186
+ }
149
187
  const transformCoordinate = model.renderable.getTransformCoordinate();
150
188
  const view = ren.getRenderWindow().getViews()[0];
151
189
  const vsize = view.getViewportSize(ren);
@@ -171,7 +209,8 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
171
209
  tcoords,
172
210
  colors: c,
173
211
  cellOffset: 0,
174
- haveCellScalars: model.haveCellSCalars,
212
+ useTCoordsPerCell,
213
+ haveCellScalars: model.renderable.getAreScalarsMappedFromCells(),
175
214
  customAttributes: model.renderable.getCustomShaderAttributes().map(arrayName => poly.getPointData().getArrayByName(arrayName))
176
215
  };
177
216
  options.cellOffset += model.primitives[primTypes.Points].getCABO().createVBO(poly.getVerts(), 'verts', representation, options);
@@ -201,6 +240,9 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
201
240
  if (model.lastBoundBO) {
202
241
  model.lastBoundBO.getVAO().release();
203
242
  }
243
+ if (model.renderable.getColorTextureMap()) {
244
+ model.internalColorTexture.deactivate();
245
+ }
204
246
  };
205
247
  publicAPI.replaceShaderValues = (shaders, ren, actor) => {
206
248
  publicAPI.replaceShaderColor(shaders, ren, actor);
@@ -212,21 +254,28 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
212
254
  let VSSource = shaders.Vertex;
213
255
  let GSSource = shaders.Geometry;
214
256
  let FSSource = shaders.Fragment;
215
- if (model.haveCellScalars) {
216
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Dec', ['uniform samplerBuffer texture1;']).result;
217
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Impl', ['gl_FragData[0] = texelFetchBuffer(texture1, gl_PrimitiveID + PrimitiveIDOffset);']).result;
218
- }
257
+
258
+ // create the color property declarations
259
+ // these are always defined
260
+ let colorDec = ['uniform vec3 diffuseColorUniform;', 'uniform float opacityUniform;'];
261
+
262
+ // now handle the more complex fragment shader implementation
263
+ let colorImpl = ['vec3 diffuseColor = diffuseColorUniform;', 'float opacity = opacityUniform;'];
264
+
265
+ // add scalar vertex colors
219
266
  if (model.lastBoundBO.getCABO().getColorComponents() !== 0) {
220
- VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Dec', ['in vec4 diffuseColor;', 'out vec4 fcolorVSOutput;']).result;
221
- VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Impl', ['fcolorVSOutput = diffuseColor;']).result;
222
- GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Color::Dec', ['in vec4 fcolorVSOutput[];\n', 'out vec4 fcolorGSOutput;']).result;
223
- GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Color::Impl', ['fcolorGSOutput = fcolorVSOutput[i];']).result;
224
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Dec', ['in vec4 fcolorVSOutput;']).result;
225
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Impl', ['gl_FragData[0] = fcolorVSOutput;']).result;
226
- } else {
227
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Dec', ['uniform vec4 diffuseColor;']).result;
228
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Impl', ['gl_FragData[0] = diffuseColor;']).result;
267
+ colorDec = colorDec.concat(['varying vec4 vertexColorVSOutput;']);
268
+ VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Dec', ['attribute vec4 scalarColor;', 'varying vec4 vertexColorVSOutput;']).result;
269
+ VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Impl', ['vertexColorVSOutput = scalarColor;']).result;
270
+ GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Color::Dec', ['in vec4 vertexColorVSOutput[];', 'out vec4 vertexColorGSOutput;']).result;
271
+ GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::Color::Impl', ['vertexColorGSOutput = vertexColorVSOutput[i];']).result;
272
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Impl', colorImpl.concat([' diffuseColor = vertexColorVSOutput.rgb;', ' opacity = opacity*vertexColorVSOutput.a;'])).result;
273
+ } else if (model.renderable.getAreScalarsMappedFromCells()) {
274
+ colorImpl = colorImpl.concat([' vec4 texColor = texture2D(texture1, tcoordVCVSOutput.st);', ' diffuseColor = texColor.rgb;', ' opacity = opacity*texColor.a;']);
229
275
  }
276
+ colorImpl = colorImpl.concat(['gl_FragData[0] = vec4(diffuseColor, opacity);']);
277
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Dec', colorDec).result;
278
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Color::Impl', colorImpl).result;
230
279
  shaders.Vertex = VSSource;
231
280
  shaders.Geometry = GSSource;
232
281
  shaders.Fragment = FSSource;
@@ -252,7 +301,7 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
252
301
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Dec', ['in vec2 tcoordVCVSOutput;', 'uniform sampler2D texture1;']).result;
253
302
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Impl', ['gl_FragData[0] = gl_FragData[0]*texture2D(texture1, tcoordVCVSOutput.st);']).result;
254
303
  }
255
- if (model.haveCellScalars) {
304
+ if (model.renderable.getAreScalarsMappedFromCells()) {
256
305
  GSSource = vtkShaderProgram.substitute(GSSource, '//VTK::PrimID::Impl', ['gl_PrimitiveID = gl_PrimitiveIDIn;']).result;
257
306
  }
258
307
  shaders.Vertex = VSSource;
@@ -303,8 +352,18 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
303
352
  } else {
304
353
  cellBO.getVAO().removeAttributeArray('tcoordMC');
305
354
  }
355
+ if (cellBO.getProgram().isAttributeUsed('scalarColor') && cellBO.getCABO().getColorComponents()) {
356
+ if (!cellBO.getVAO().addAttributeArray(cellBO.getProgram(), cellBO.getCABO().getColorBO(), 'scalarColor', cellBO.getCABO().getColorOffset(), cellBO.getCABO().getColorBOStride(), model.context.UNSIGNED_BYTE, 4, true)) {
357
+ vtkErrorMacro('Error setting scalarColor in shader VAO.');
358
+ }
359
+ } else {
360
+ cellBO.getVAO().removeAttributeArray('scalarColor');
361
+ }
306
362
  if (model.internalColorTexture && cellBO.getProgram().isUniformUsed('texture1')) {
307
- cellBO.getProgram().setUniformi('texture1', model.internalColorTexture.getTextureUnit());
363
+ const texUnit = model.internalColorTexture.getTextureUnit();
364
+ if (texUnit > -1) {
365
+ cellBO.getProgram().setUniformi('texture1', model.internalColorTexture.getTextureUnit());
366
+ }
308
367
  }
309
368
  const tus = model.openGLActor2D.getActiveTextures();
310
369
  if (tus) {
@@ -331,9 +390,9 @@ function vtkOpenGLPolyDataMapper2D(publicAPI, model) {
331
390
  const program = cellBO.getProgram();
332
391
  const ppty = actor.getProperty();
333
392
  const opacity = ppty.getOpacity();
393
+ program.setUniformf('opacityUniform', opacity);
334
394
  const dColor = ppty.getColor();
335
- const diffuseColor = [dColor[0], dColor[1], dColor[2], opacity];
336
- program.setUniform4f('diffuseColor', diffuseColor);
395
+ program.setUniform3fArray('diffuseColorUniform', dColor);
337
396
  }
338
397
  };
339
398
  publicAPI.setLightingShaderParameters = (cellBO, ren, actor) => {
package/index.d.ts CHANGED
@@ -75,6 +75,7 @@
75
75
  /// <reference path="./Filters/Sources/CubeSource.d.ts" />
76
76
  /// <reference path="./Filters/Sources/Cursor3D.d.ts" />
77
77
  /// <reference path="./Filters/Sources/CylinderSource.d.ts" />
78
+ /// <reference path="./Filters/Sources/DiskSource.d.ts" />
78
79
  /// <reference path="./Filters/Sources/LineSource.d.ts" />
79
80
  /// <reference path="./Filters/Sources/PlaneSource.d.ts" />
80
81
  /// <reference path="./Filters/Sources/PointSource.d.ts" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "34.3.1",
3
+ "version": "34.5.0",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",