@kitware/vtk.js 33.2.0 → 34.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/BREAKING_CHANGES.md +10 -0
  2. package/Interaction/Manipulators/KeyboardCameraManipulator.d.ts +113 -0
  3. package/Rendering/Core/Actor.d.ts +5 -20
  4. package/Rendering/Core/Actor.js +5 -68
  5. package/Rendering/Core/ImageCPRMapper.d.ts +1 -20
  6. package/Rendering/Core/ImageCPRMapper.js +1 -2
  7. package/Rendering/Core/ImageProperty.d.ts +20 -1
  8. package/Rendering/Core/ImageProperty.js +7 -5
  9. package/Rendering/Core/ImageResliceMapper.d.ts +1 -20
  10. package/Rendering/Core/ImageResliceMapper.js +1 -2
  11. package/Rendering/Core/ImageSlice.d.ts +7 -23
  12. package/Rendering/Core/ImageSlice.js +9 -68
  13. package/Rendering/Core/Prop3D.d.ts +39 -2
  14. package/Rendering/Core/Prop3D.js +81 -2
  15. package/Rendering/Core/RenderWindowInteractor.d.ts +6 -0
  16. package/Rendering/Core/RenderWindowInteractor.js +7 -5
  17. package/Rendering/Core/Volume.d.ts +5 -20
  18. package/Rendering/Core/Volume.js +2 -70
  19. package/Rendering/Core/VolumeMapper/Constants.d.ts +0 -7
  20. package/Rendering/Core/VolumeMapper/Constants.js +2 -8
  21. package/Rendering/Core/VolumeMapper.d.ts +16 -173
  22. package/Rendering/Core/VolumeMapper.js +16 -51
  23. package/Rendering/Core/VolumeProperty/Constants.d.ts +12 -3
  24. package/Rendering/Core/VolumeProperty/Constants.js +11 -4
  25. package/Rendering/Core/VolumeProperty.d.ts +140 -5
  26. package/Rendering/Core/VolumeProperty.js +54 -7
  27. package/Rendering/OpenGL/Framebuffer.js +7 -1
  28. package/Rendering/OpenGL/ImageCPRMapper.js +72 -27
  29. package/Rendering/OpenGL/ImageMapper.js +71 -33
  30. package/Rendering/OpenGL/ImageResliceMapper.js +306 -183
  31. package/Rendering/OpenGL/OrderIndependentTranslucentPass.js +20 -3
  32. package/Rendering/OpenGL/PolyDataMapper.js +8 -9
  33. package/Rendering/OpenGL/RenderWindow/resourceSharingHelper.d.ts +3 -3
  34. package/Rendering/OpenGL/RenderWindow/resourceSharingHelper.js +8 -5
  35. package/Rendering/OpenGL/SurfaceLIC/LineIntegralConvolution2D/pingpong.js +7 -1
  36. package/Rendering/OpenGL/SurfaceLIC/SurfaceLICInterface.js +20 -3
  37. package/Rendering/OpenGL/Texture.d.ts +110 -62
  38. package/Rendering/OpenGL/Texture.js +145 -37
  39. package/Rendering/OpenGL/VolumeMapper.js +763 -783
  40. package/Rendering/OpenGL/glsl/vtkVolumeFS.glsl.js +1 -1
  41. package/Rendering/WebGPU/VolumePassFSQ.js +2 -2
  42. package/index.d.ts +1 -0
  43. package/macros.js +1 -1
  44. package/macros2.js +8 -3
  45. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  import { n as newInstance$1, o as obj, e as setGet, h as chain, c as macro } from '../../macros2.js';
2
2
  import DeepEqual from 'fast-deep-equal';
3
3
  import { mat4, mat3, vec3 } from 'gl-matrix';
4
+ import vtkBoundingBox from '../../Common/DataModel/BoundingBox.js';
4
5
  import vtkDataArray from '../../Common/Core/DataArray.js';
5
6
  import { VtkDataTypes } from '../../Common/Core/DataArray/Constants.js';
6
7
  import vtkHelper from './Helper.js';
7
- import { u as uninitializeBounds } from '../../Common/Core/Math/index.js';
8
8
  import vtkOpenGLFramebuffer from './Framebuffer.js';
9
9
  import vtkOpenGLTexture from './Texture.js';
10
10
  import vtkReplacementShaderMapper from './ReplacementShaderMapper.js';
@@ -13,9 +13,9 @@ import vtkVertexArrayObject from './VertexArrayObject.js';
13
13
  import vtkViewNode from '../SceneGraph/ViewNode.js';
14
14
  import { Representation } from '../Core/Property/Constants.js';
15
15
  import { Wrap, Filter } from './Texture/Constants.js';
16
- import { InterpolationType, OpacityMode, ColorMixPreset } from '../Core/VolumeProperty/Constants.js';
16
+ import { ColorMixPreset, OpacityMode, InterpolationType } from '../Core/VolumeProperty/Constants.js';
17
17
  import { BlendMode } from '../Core/VolumeMapper/Constants.js';
18
- import { getTransferFunctionHash, getImageDataHash } from './RenderWindow/resourceSharingHelper.js';
18
+ import { getTransferFunctionsHash, getImageDataHash } from './RenderWindow/resourceSharingHelper.js';
19
19
  import { v as vtkVolumeVS } from './glsl/vtkVolumeVS.glsl.js';
20
20
  import { v as vtkVolumeFS } from './glsl/vtkVolumeFS.glsl.js';
21
21
  import { registerOverride } from './ViewNodeFactory.js';
@@ -29,81 +29,14 @@ const {
29
29
  // helper methods
30
30
  // ----------------------------------------------------------------------------
31
31
 
32
- function getColorCodeFromPreset(colorMixPreset) {
33
- switch (colorMixPreset) {
34
- case ColorMixPreset.CUSTOM:
35
- return '//VTK::CustomColorMix';
36
- case ColorMixPreset.ADDITIVE:
37
- return `
38
- // compute normals
39
- mat4 normalMat = computeMat4Normal(posIS, tValue, tstep);
40
- #if (vtkLightComplexity > 0) && defined(vtkComputeNormalFromOpacity)
41
- vec3 scalarInterp0[2];
42
- vec4 normalLight0 = computeNormalForDensity(posIS, tstep, scalarInterp0, 0);
43
- scalarInterp0[0] = scalarInterp0[0] * oscale0 + oshift0;
44
- scalarInterp0[1] = scalarInterp0[1] * oscale0 + oshift0;
45
- normalLight0 = computeDensityNormal(scalarInterp0, height0, 1.0);
46
-
47
- vec3 scalarInterp1[2];
48
- vec4 normalLight1 = computeNormalForDensity(posIS, tstep, scalarInterp1, 1);
49
- scalarInterp1[0] = scalarInterp1[0] * oscale1 + oshift1;
50
- scalarInterp1[1] = scalarInterp1[1] * oscale1 + oshift1;
51
- normalLight1 = computeDensityNormal(scalarInterp1, height1, 1.0);
52
- #else
53
- vec4 normalLight0 = normalMat[0];
54
- vec4 normalLight1 = normalMat[1];
55
- #endif
56
-
57
- // compute opacities
58
- float opacity0 = pwfValue0;
59
- float opacity1 = pwfValue1;
60
- #ifdef vtkGradientOpacityOn
61
- float gof0 = computeGradientOpacityFactor(normalMat[0].a, goscale0, goshift0, gomin0, gomax0);
62
- opacity0 *= gof0;
63
- float gof1 = computeGradientOpacityFactor(normalMat[1].a, goscale1, goshift1, gomin1, gomax1);
64
- opacity1 *= gof1;
65
- #endif
66
- float opacitySum = opacity0 + opacity1;
67
- if (opacitySum <= 0.0) {
68
- return vec4(0.0);
69
- }
70
-
71
- // mix the colors and opacities
72
- tColor0 = applyAllLightning(tColor0, opacity0, posIS, normalLight0);
73
- tColor1 = applyAllLightning(tColor1, opacity1, posIS, normalLight1);
74
- vec3 mixedColor = (opacity0 * tColor0 + opacity1 * tColor1) / opacitySum;
75
- return vec4(mixedColor, min(1.0, opacitySum));
76
- `;
77
- case ColorMixPreset.COLORIZE:
78
- return `
79
- // compute normals
80
- mat4 normalMat = computeMat4Normal(posIS, tValue, tstep);
81
- #if (vtkLightComplexity > 0) && defined(vtkComputeNormalFromOpacity)
82
- vec3 scalarInterp0[2];
83
- vec4 normalLight0 = computeNormalForDensity(posIS, tstep, scalarInterp0, 0);
84
- scalarInterp0[0] = scalarInterp0[0] * oscale0 + oshift0;
85
- scalarInterp0[1] = scalarInterp0[1] * oscale0 + oshift0;
86
- normalLight0 = computeDensityNormal(scalarInterp0, height0, 1.0);
87
- #else
88
- vec4 normalLight0 = normalMat[0];
89
- #endif
90
-
91
- // compute opacities
92
- float opacity0 = pwfValue0;
93
- #ifdef vtkGradientOpacityOn
94
- float gof0 = computeGradientOpacityFactor(normalMat[0].a, goscale0, goshift0, gomin0, gomax0);
95
- opacity0 *= gof0;
96
- #endif
97
-
98
- // mix the colors and opacities
99
- vec3 color = tColor0 * mix(vec3(1.0), tColor1, pwfValue1);
100
- color = applyAllLightning(color, opacity0, posIS, normalLight0);
101
- return vec4(color, opacity0);
102
- `;
103
- default:
104
- return null;
105
- }
106
- }
32
+ // Some matrices to avoid reallocations when we need them
33
+ const preAllocatedMatrices = {
34
+ idxToView: mat4.identity(new Float64Array(16)),
35
+ vecISToVCMatrix: mat3.identity(new Float64Array(9)),
36
+ modelToView: mat4.identity(new Float64Array(16)),
37
+ projectionToView: mat4.identity(new Float64Array(16)),
38
+ projectionToWorld: mat4.identity(new Float64Array(16))
39
+ };
107
40
 
108
41
  // ----------------------------------------------------------------------------
109
42
  // vtkOpenGLVolumeMapper methods
@@ -112,8 +45,51 @@ function getColorCodeFromPreset(colorMixPreset) {
112
45
  function vtkOpenGLVolumeMapper(publicAPI, model) {
113
46
  // Set our className
114
47
  model.classHierarchy.push('vtkOpenGLVolumeMapper');
48
+ function getUseIndependentComponents(actorProperty, numComp) {
49
+ const iComps = actorProperty.getIndependentComponents();
50
+ const colorMixPreset = actorProperty.getColorMixPreset();
51
+ return iComps && numComp >= 2 || !!colorMixPreset;
52
+ }
53
+ function isLabelmapOutlineRequired(actorProperty) {
54
+ return actorProperty.getUseLabelOutline() || model.renderable.getBlendMode() === BlendMode.LABELMAP_EDGE_PROJECTION_BLEND;
55
+ }
56
+
57
+ // Associate a reference counter to each graphics resource
58
+ const graphicsResourceReferenceCount = new Map();
59
+ function decreaseGraphicsResourceCount(openGLRenderWindow, coreObject) {
60
+ if (!coreObject) {
61
+ return;
62
+ }
63
+ const oldCount = graphicsResourceReferenceCount.get(coreObject) ?? 0;
64
+ const newCount = oldCount - 1;
65
+ if (newCount <= 0) {
66
+ openGLRenderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI);
67
+ graphicsResourceReferenceCount.delete(coreObject);
68
+ } else {
69
+ graphicsResourceReferenceCount.set(coreObject, newCount);
70
+ }
71
+ }
72
+ function increaseGraphicsResourceCount(openGLRenderWindow, coreObject) {
73
+ if (!coreObject) {
74
+ return;
75
+ }
76
+ const oldCount = graphicsResourceReferenceCount.get(coreObject) ?? 0;
77
+ const newCount = oldCount + 1;
78
+ graphicsResourceReferenceCount.set(coreObject, newCount);
79
+ if (oldCount <= 0) {
80
+ openGLRenderWindow.registerGraphicsResourceUser(coreObject, publicAPI);
81
+ }
82
+ }
83
+ function replaceGraphicsResource(openGLRenderWindow, oldResourceCoreObject, newResourceCoreObject) {
84
+ if (oldResourceCoreObject === newResourceCoreObject) {
85
+ return;
86
+ }
87
+ decreaseGraphicsResourceCount(openGLRenderWindow, oldResourceCoreObject);
88
+ increaseGraphicsResourceCount(openGLRenderWindow, newResourceCoreObject);
89
+ }
115
90
  function unregisterGraphicsResources(renderWindow) {
116
- [model._scalars, model._scalarOpacityFunc, model._colorTransferFunc, model._labelOutlineThicknessArray].forEach(coreObject => renderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI));
91
+ // Convert to an array using the spread operator as Firefox doesn't support Iterator.forEach()
92
+ [...graphicsResourceReferenceCount.keys()].forEach(coreObject => renderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI));
117
93
  }
118
94
  publicAPI.buildPass = () => {
119
95
  model.zBufferTexture = null;
@@ -157,216 +133,175 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
157
133
  shaders.Fragment = vtkVolumeFS;
158
134
  shaders.Geometry = '';
159
135
  };
160
- publicAPI.useIndependentComponents = actorProperty => {
161
- const iComps = actorProperty.getIndependentComponents();
162
- const image = model.currentInput;
163
- const numComp = image?.getPointData()?.getScalars()?.getNumberOfComponents();
164
- const colorMixPreset = actorProperty.getColorMixPreset();
165
- return iComps && numComp >= 2 || !!colorMixPreset;
166
- };
167
136
  publicAPI.replaceShaderValues = (shaders, ren, actor) => {
168
- const actorProps = actor.getProperty();
169
137
  let FSSource = shaders.Fragment;
170
-
171
- // define some values in the shader
172
- const iType = actorProps.getInterpolationType();
173
- if (iType === InterpolationType.LINEAR) {
174
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TrilinearOn', '#define vtkTrilinearOn').result;
138
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::EnabledColorFunctions', `#define EnableColorForValueFunctionId${model.previousState.colorForValueFunctionId}`).result;
139
+ const enabledLightings = [];
140
+ if (model.previousState.surfaceLightingEnabled) {
141
+ enabledLightings.push('Surface');
175
142
  }
176
- const vtkImageLabelOutline = publicAPI.isLabelmapOutlineRequired(actor);
177
- if (vtkImageLabelOutline === true) {
178
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ImageLabelOutlineOn', '#define vtkImageLabelOutlineOn').result;
143
+ if (model.previousState.volumeLightingEnabled) {
144
+ enabledLightings.push('Volume');
179
145
  }
180
- const LabelEdgeProjection = model.renderable.getBlendMode() === BlendMode.LABELMAP_EDGE_PROJECTION_BLEND;
181
- if (LabelEdgeProjection) {
182
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LabelEdgeProjectionOn', '#define vtkLabelEdgeProjectionOn').result;
146
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::EnabledLightings', enabledLightings.map(lightingType => `#define Enable${lightingType}Lighting`)).result;
147
+ if (model.previousState.multiTexturePerVolumeEnabled) {
148
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::EnabledMultiTexturePerVolume', '#define EnabledMultiTexturePerVolume').result;
183
149
  }
184
- const numComp = model.scalarTexture.getComponents();
185
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::NumComponents', `#define vtkNumComponents ${numComp}`).result;
186
- const useIndependentComps = publicAPI.useIndependentComponents(actorProps);
187
- if (useIndependentComps) {
188
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::IndependentComponentsOn', '#define UseIndependentComponents').result;
150
+ if (model.previousState.useIndependentComponents) {
151
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::EnabledIndependentComponents', '#define EnabledIndependentComponents').result;
189
152
  }
190
-
191
- // Define any proportional components
192
- const proportionalComponents = [];
193
- const forceNearestComponents = [];
194
- for (let nc = 0; nc < numComp; nc++) {
195
- if (actorProps.getOpacityMode(nc) === OpacityMode.PROPORTIONAL) {
196
- proportionalComponents.push(`#define vtkComponent${nc}Proportional`);
197
- }
198
- if (actorProps.getForceNearestInterpolation(nc)) {
199
- forceNearestComponents.push(`#define vtkComponent${nc}ForceNearest`);
200
- }
201
- }
202
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::vtkProportionalComponents', proportionalComponents.join('\n')).result;
203
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::vtkForceNearestComponents', forceNearestComponents.join('\n')).result;
204
- const colorMixPreset = actorProps.getColorMixPreset();
205
- const colorMixCode = getColorCodeFromPreset(colorMixPreset);
206
- if (colorMixCode) {
207
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::CustomComponentsColorMixOn', '#define vtkCustomComponentsColorMix').result;
208
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::CustomComponentsColorMix::Impl', colorMixCode).result;
209
- }
210
-
211
- // WebGL only supports loops over constants
212
- // and does not support while loops so we
213
- // have to hard code how many steps/samples to take
214
- // We do a break so most systems will gracefully
215
- // early terminate, but it is always possible
216
- // a system will execute every step regardless
217
- const ext = model.currentInput.getSpatialExtent();
218
- const spc = model.currentInput.getSpacing();
219
- const vsize = new Float64Array(3);
220
- vec3.set(vsize, (ext[1] - ext[0]) * spc[0], (ext[3] - ext[2]) * spc[1], (ext[5] - ext[4]) * spc[2]);
221
- const maxSamples = vec3.length(vsize) / publicAPI.getCurrentSampleDistance(ren);
222
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::MaximumSamplesValue', `${Math.ceil(maxSamples)}`).result;
223
-
224
- // set light complexity
225
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LightComplexity', `#define vtkLightComplexity ${model.lightComplexity}`).result;
226
-
227
- // set shadow blending flag
228
- if (model.lightComplexity > 0) {
229
- if (model.renderable.getVolumetricScatteringBlending() > 0.0) {
230
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::VolumeShadowOn', `#define VolumeShadowOn`).result;
231
- }
232
- if (model.renderable.getVolumetricScatteringBlending() < 1.0) {
233
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::SurfaceShadowOn', `#define SurfaceShadowOn`).result;
234
- }
235
- if (model.renderable.getLocalAmbientOcclusion() && actorProps.getAmbient() > 0.0) {
236
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::localAmbientOcclusionOn', `#define localAmbientOcclusionOn`).result;
237
- }
238
- }
239
-
240
- // if using gradient opacity define that
241
- const numIComps = useIndependentComps ? numComp : 1;
242
- model.gopacity = false;
243
- for (let nc = 0; !model.gopacity && nc < numIComps; ++nc) {
244
- model.gopacity ||= actorProps.getUseGradientOpacity(nc);
245
- }
246
- if (model.gopacity) {
247
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::GradientOpacityOn', '#define vtkGradientOpacityOn').result;
248
- }
249
-
250
- // set normal from density
251
- if (model.renderable.getComputeNormalFromOpacity()) {
252
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::vtkComputeNormalFromOpacity', `#define vtkComputeNormalFromOpacity`).result;
153
+ if (model.previousState.gradientOpacityEnabled) {
154
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::EnabledGradientOpacity', '#define EnabledGradientOpacity').result;
253
155
  }
156
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::vtkProportionalComponents', model.previousState.proportionalComponents.map(component => `#define vtkComponent${component}Proportional`).join('\n')).result;
157
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::vtkForceNearestComponents', model.previousState.forceNearestComponents.map(component => `#define vtkComponent${component}ForceNearest`).join('\n')).result;
254
158
 
255
159
  // if we have a ztexture then declare it and use it
256
- if (model.zBufferTexture !== null) {
160
+ if (model.previousState.hasZBufferTexture) {
257
161
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Dec', ['uniform sampler2D zBufferTexture;', 'uniform float vpZWidth;', 'uniform float vpZHeight;']).result;
258
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Impl', ['vec4 depthVec = texture2D(zBufferTexture, vec2(gl_FragCoord.x / vpZWidth, gl_FragCoord.y/vpZHeight));', 'float zdepth = (depthVec.r*256.0 + depthVec.g)/257.0;', 'zdepth = zdepth * 2.0 - 1.0;', 'if (cameraParallel == 0) {', 'zdepth = -2.0 * camFar * camNear / (zdepth*(camFar-camNear)-(camFar+camNear)) - camNear;}', 'else {', 'zdepth = (zdepth + 1.0) * 0.5 * (camFar - camNear);}\n', 'zdepth = -zdepth/rayDir.z;', 'dists.y = min(zdepth,dists.y);']).result;
162
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Impl', ['vec4 depthVec = texture2D(zBufferTexture, vec2(gl_FragCoord.x / vpZWidth, gl_FragCoord.y/vpZHeight));', 'float zdepth = (depthVec.r*256.0 + depthVec.g)/257.0;', 'zdepth = zdepth * 2.0 - 1.0;', 'if (cameraParallel == 0) {', 'zdepth = -2.0 * camFar * camNear / (zdepth*(camFar-camNear)-(camFar+camNear)) - camNear;}', 'else {', 'zdepth = (zdepth + 1.0) * 0.5 * (camFar - camNear);}\n', 'zdepth = -zdepth/rayDirVC.z;', 'dists.y = min(zdepth,dists.y);']).result;
259
163
  }
260
164
 
261
165
  // Set the BlendMode approach
262
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::BlendMode', `${model.renderable.getBlendMode()}`).result;
263
- shaders.Fragment = FSSource;
264
- publicAPI.replaceShaderLight(shaders, ren, actor);
265
- publicAPI.replaceShaderClippingPlane(shaders, ren, actor);
266
- };
267
- publicAPI.replaceShaderLight = (shaders, ren, actor) => {
268
- if (model.lightComplexity === 0) {
269
- return;
270
- }
271
- let FSSource = shaders.Fragment;
272
- // check for shadow maps - not implemented yet, skip
273
- // const shadowFactor = '';
274
-
275
- // to-do: single out the case when complexity = 1
276
-
277
- // only account for lights that are switched on
278
- let lightNum = 0;
279
- ren.getLights().forEach(light => {
280
- if (light.getSwitch()) {
281
- lightNum += 1;
282
- }
283
- });
284
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Light::Dec', [`uniform int lightNum;`, `uniform bool twoSidedLighting;`, `uniform vec3 lightColor[${lightNum}];`, `uniform vec3 lightDirectionVC[${lightNum}]; // normalized`, `uniform vec3 lightHalfAngleVC[${lightNum}];`, '//VTK::Light::Dec'], false).result;
285
- // support any number of lights
286
- if (model.lightComplexity === 3) {
287
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Light::Dec', [`uniform vec3 lightPositionVC[${lightNum}];`, `uniform vec3 lightAttenuation[${lightNum}];`, `uniform float lightConeAngle[${lightNum}];`, `uniform float lightExponent[${lightNum}];`, `uniform int lightPositional[${lightNum}];`], false).result;
288
- }
289
- if (model.renderable.getVolumetricScatteringBlending() > 0.0) {
290
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::VolumeShadow::Dec', [`uniform float volumetricScatteringBlending;`, `uniform float giReach;`, `uniform float volumeShadowSamplingDistFactor;`, `uniform float anisotropy;`, `uniform float anisotropy2;`], false).result;
291
- }
292
- if (model.renderable.getLocalAmbientOcclusion() && actor.getProperty().getAmbient() > 0.0) {
293
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LAO::Dec', [`uniform int kernelRadius;`, `uniform vec2 kernelSample[${model.renderable.getLAOKernelRadius()}];`, `uniform int kernelSize;`], false).result;
294
- }
166
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::BlendMode', `${model.previousState.blendMode}`).result;
167
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::NumberOfLights', `${model.previousState.numberOfLights}`).result;
168
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::MaxLaoKernelSize', `${model.previousState.maxLaoKernelSize}`).result;
169
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::NumberOfComponents', `${model.previousState.numberOfComponents}`).result;
170
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::MaximumNumberOfSamples', `${model.previousState.maximumNumberOfSamples}`).result;
295
171
  shaders.Fragment = FSSource;
296
- };
297
- publicAPI.replaceShaderClippingPlane = (shaders, ren, actor) => {
298
- let FSSource = shaders.Fragment;
299
- if (model.renderable.getClippingPlanes().length > 0) {
300
- const clipPlaneSize = model.renderable.getClippingPlanes().length;
172
+ const numberOfClippingPlanes = model.previousState.numberOfClippingPlanes;
173
+ if (numberOfClippingPlanes > 0) {
301
174
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ClipPlane::Dec', [`uniform vec3 vClipPlaneNormals[6];`, `uniform float vClipPlaneDistances[6];`, `uniform vec3 vClipPlaneOrigins[6];`, `uniform int clip_numPlanes;`, '//VTK::ClipPlane::Dec', '#define vtkClippingPlanesOn'], false).result;
302
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ClipPlane::Impl', [`for(int i = 0; i < ${clipPlaneSize}; i++) {`, ' float rayDirRatio = dot(rayDir, vClipPlaneNormals[i]);', ' float equationResult = dot(vertexVCVSOutput, vClipPlaneNormals[i]) + vClipPlaneDistances[i];', ' if (rayDirRatio == 0.0)', ' {', ' if (equationResult < 0.0) dists.x = dists.y;', ' continue;', ' }', ' float result = -1.0 * equationResult / rayDirRatio;', ' if (rayDirRatio < 0.0) dists.y = min(dists.y, result);', ' else dists.x = max(dists.x, result);', '}', '//VTK::ClipPlane::Impl'], false).result;
175
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ClipPlane::Impl', [`for(int i = 0; i < ${numberOfClippingPlanes}; i++) {`, ' float rayDirRatio = dot(rayDirVC, vClipPlaneNormals[i]);', ' float equationResult = dot(vertexVCVSOutput, vClipPlaneNormals[i]) + vClipPlaneDistances[i];', ' if (rayDirRatio == 0.0)', ' {', ' if (equationResult < 0.0) dists.x = dists.y;', ' continue;', ' }', ' float result = -1.0 * equationResult / rayDirRatio;', ' if (rayDirRatio < 0.0) dists.y = min(dists.y, result);', ' else dists.x = max(dists.x, result);', '}', '//VTK::ClipPlane::Impl'], false).result;
303
176
  }
304
177
  shaders.Fragment = FSSource;
305
178
  };
306
- const recomputeLightComplexity = (actor, lights) => {
307
- // do we need lighting?
308
- let lightComplexity = 0;
309
- if (actor.getProperty().getShade() && model.renderable.getBlendMode() === BlendMode.COMPOSITE_BLEND) {
310
- // consider the lighting complexity to determine which case applies
311
- // simple headlight, Light Kit, the whole feature set of VTK
312
- lightComplexity = 0;
313
- model.numberOfLights = 0;
314
- lights.forEach(light => {
315
- const status = light.getSwitch();
316
- if (status > 0) {
317
- model.numberOfLights++;
318
- if (lightComplexity === 0) {
319
- lightComplexity = 1;
320
- }
321
- }
322
- if (lightComplexity === 1 && (model.numberOfLights > 1 || light.getIntensity() !== 1.0 || !light.lightTypeIsHeadLight())) {
323
- lightComplexity = 2;
324
- }
325
- if (lightComplexity < 3 && light.getPositional()) {
326
- lightComplexity = 3;
327
- }
328
- });
179
+ publicAPI.getNeedToRebuildShaders = (cellBO, ren, actor) => {
180
+ // These are all the variables that fully determine the behavior of replaceShaderValues
181
+ // and the exact content of the shader
182
+ // See replaceShaderValues method
183
+ const hasZBufferTexture = !!model.zBufferTexture;
184
+ const numberOfValidInputs = model.currentValidInputs.length;
185
+ const numberOfLights = model.numberOfLights;
186
+ const numberOfComponents = model.numberOfComponents;
187
+ const useIndependentComponents = model.useIndependentComponents;
188
+
189
+ // The volume property that is used is always the first one
190
+ const volumeProperties = actor.getProperties();
191
+ const firstValidInput = model.currentValidInputs[0];
192
+ const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex];
193
+
194
+ // There are two modes:
195
+ // - single volume with multiple components
196
+ // - multiple volumes with one component per volume
197
+ const multiTexturePerVolumeEnabled = numberOfValidInputs > 1;
198
+
199
+ // Get maximum number of samples
200
+ const boundsMC = firstValidInput.imageData.getBounds();
201
+ const maximumRayLength = vtkBoundingBox.getDiagonalLength(boundsMC);
202
+ const maximumNumberOfSamples = Math.ceil(maximumRayLength / publicAPI.getCurrentSampleDistance(ren));
203
+ if (maximumNumberOfSamples > model.renderable.getMaximumSamplesPerRay()) {
204
+ vtkWarningMacro(`The number of steps required ${maximumNumberOfSamples} is larger than the ` + `specified maximum number of steps ${model.renderable.getMaximumSamplesPerRay()}.\n` + 'Please either change the volumeMapper sampleDistance or its maximum number of samples.');
205
+ }
206
+
207
+ // Gradient opacity
208
+ const numberOfIndependantComponents = useIndependentComponents ? numberOfComponents : 1;
209
+ let gradientOpacityEnabled = false;
210
+ for (let i = 0; i < numberOfIndependantComponents; ++i) {
211
+ if (firstVolumeProperty.getUseGradientOpacity(i)) {
212
+ gradientOpacityEnabled = true;
213
+ break;
214
+ }
329
215
  }
330
- if (lightComplexity !== model.lightComplexity) {
331
- model.lightComplexity = lightComplexity;
332
- publicAPI.modified();
216
+
217
+ // Get the max kernel size from volume properties that use LAO and
218
+ // that are linked to a valid input imageData
219
+ let maxLaoKernelSize = 0;
220
+ const kernelSize = firstVolumeProperty.getLAOKernelSize();
221
+ if (kernelSize > maxLaoKernelSize && firstVolumeProperty.getLocalAmbientOcclusion() && firstVolumeProperty.getAmbient() > 0.0) {
222
+ maxLaoKernelSize = kernelSize;
223
+ }
224
+ const numberOfClippingPlanes = model.renderable.getClippingPlanes().length;
225
+ // These are from the buildShader function in vtkReplacementShaderMapper
226
+ const mapperShaderReplacements = model.renderable.getViewSpecificProperties().OpenGL?.ShaderReplacements;
227
+ const renderPassShaderReplacements = model.currentRenderPass?.getShaderReplacement();
228
+ const blendMode = model.renderable.getBlendMode();
229
+
230
+ // This enables optimizing out some function which avoids huge shader compilation time
231
+ // The result of this computation is used in getColorForValue in the fragment shader
232
+ const colorForValueFunctionId = (() => {
233
+ // If labeloutline and not the edge labelmap, since in the edge labelmap blend
234
+ // we need the underlying data to sample through
235
+ if (blendMode !== BlendMode.LABELMAP_EDGE_PROJECTION_BLEND && isLabelmapOutlineRequired(firstVolumeProperty)) {
236
+ return 5;
237
+ }
238
+ if (useIndependentComponents) {
239
+ switch (firstVolumeProperty.getColorMixPreset()) {
240
+ case ColorMixPreset.ADDITIVE:
241
+ return 1;
242
+ case ColorMixPreset.COLORIZE:
243
+ return 2;
244
+ case ColorMixPreset.CUSTOM:
245
+ return 3;
246
+ default:
247
+ // ColorMixPreset.DEFAULT
248
+ return 4;
249
+ }
250
+ }
251
+ return 0;
252
+ })();
253
+
254
+ // Get which types of lighting are enabled
255
+ const surfaceLightingEnabled = firstVolumeProperty.getVolumetricScatteringBlending() < 1.0;
256
+ const volumeLightingEnabled = firstVolumeProperty.getVolumetricScatteringBlending() > 0.0;
257
+
258
+ // Is any volume using ForceNearestInterpolation
259
+ let forceNearestInterpolationEnabled = false;
260
+ for (let component = 0; component < numberOfComponents; ++component) {
261
+ if (firstVolumeProperty.getForceNearestInterpolation(component)) {
262
+ forceNearestInterpolationEnabled = true;
263
+ break;
264
+ }
333
265
  }
334
- };
335
- publicAPI.getNeedToRebuildShaders = (cellBO, ren, actor) => {
336
- const actorProps = actor.getProperty();
337
- recomputeLightComplexity(actor, ren.getLights());
338
- const numComp = model.scalarTexture.getComponents();
339
- const opacityModes = [];
340
- const forceNearestInterps = [];
341
- for (let nc = 0; nc < numComp; nc++) {
342
- opacityModes.push(actorProps.getOpacityMode(nc));
343
- forceNearestInterps.push(actorProps.getForceNearestInterpolation(nc));
266
+
267
+ // Define any proportional components
268
+ const proportionalComponents = [];
269
+ const forceNearestComponents = [];
270
+ for (let component = 0; component < numberOfComponents; component++) {
271
+ if (firstVolumeProperty.getOpacityMode(component) === OpacityMode.PROPORTIONAL) {
272
+ proportionalComponents.push(component);
273
+ }
274
+ if (firstVolumeProperty.getForceNearestInterpolation(component)) {
275
+ forceNearestComponents.push(component);
276
+ }
344
277
  }
345
- const ext = model.currentInput.getSpatialExtent();
346
- const spc = model.currentInput.getSpacing();
347
- const vsize = new Float64Array(3);
348
- vec3.set(vsize, (ext[1] - ext[0]) * spc[0], (ext[3] - ext[2]) * spc[1], (ext[5] - ext[4]) * spc[2]);
349
- const maxSamples = vec3.length(vsize) / publicAPI.getCurrentSampleDistance(ren);
350
- const hasZBufferTexture = !!model.zBufferTexture;
351
- const state = {
352
- iComps: actorProps.getIndependentComponents(),
353
- colorMixPreset: actorProps.getColorMixPreset(),
354
- interpolationType: actorProps.getInterpolationType(),
355
- useLabelOutline: publicAPI.isLabelmapOutlineRequired(actor),
356
- numComp,
357
- maxSamples,
358
- useGradientOpacity: actorProps.getUseGradientOpacity(0),
359
- blendMode: model.renderable.getBlendMode(),
278
+ const currentState = {
279
+ numberOfComponents,
280
+ useIndependentComponents,
281
+ proportionalComponents,
282
+ forceNearestComponents,
283
+ blendMode,
284
+ numberOfLights,
285
+ numberOfValidInputs,
286
+ maximumNumberOfSamples,
360
287
  hasZBufferTexture,
361
- opacityModes,
362
- forceNearestInterps
288
+ maxLaoKernelSize,
289
+ numberOfClippingPlanes,
290
+ mapperShaderReplacements,
291
+ renderPassShaderReplacements,
292
+ colorForValueFunctionId,
293
+ surfaceLightingEnabled,
294
+ volumeLightingEnabled,
295
+ forceNearestInterpolationEnabled,
296
+ multiTexturePerVolumeEnabled,
297
+ gradientOpacityEnabled
363
298
  };
364
299
 
365
300
  // We need to rebuild the shader if one of these variables has changed,
366
301
  // since they are used in the shader template replacement step.
367
302
  // We also need to rebuild if the shader source time is outdated.
368
- if (cellBO.getProgram()?.getHandle() === 0 || cellBO.getShaderSourceTime().getMTime() < publicAPI.getMTime() || cellBO.getShaderSourceTime().getMTime() < model.renderable.getMTime() || !model.previousState || !DeepEqual(model.previousState, state)) {
369
- model.previousState = state;
303
+ if (cellBO.getProgram()?.getHandle() === 0 || !model.previousState || !DeepEqual(model.previousState, currentState)) {
304
+ model.previousState = currentState;
370
305
  return true;
371
306
  }
372
307
  return false;
@@ -411,27 +346,49 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
411
346
  }
412
347
  cellBO.getAttributeUpdateTime().modified();
413
348
  }
414
- program.setUniformi('texture1', model.scalarTexture.getTextureUnit());
415
- program.setUniformf('sampleDistance', publicAPI.getCurrentSampleDistance(ren));
416
- const volInfo = model.scalarTexture.getVolumeInfo();
417
- const ipScalarRange = model.renderable.getIpScalarRange();
418
-
419
- // In some situations, we might not have computed the scale and offset
420
- // for the data range, or it might not be needed.
421
- if (volInfo?.dataComputedScale?.length) {
422
- const minVals = [];
423
- const maxVals = [];
424
- for (let i = 0; i < 4; i++) {
349
+ const sampleDistance = publicAPI.getCurrentSampleDistance(ren);
350
+ program.setUniformf('sampleDistance', sampleDistance);
351
+ const volumeShadowSampleDistance = sampleDistance * model.renderable.getVolumeShadowSamplingDistFactor();
352
+ program.setUniformf('volumeShadowSampleDistance', volumeShadowSampleDistance);
353
+
354
+ // Volume textures
355
+ model.scalarTextures.forEach((scalarTexture, component) => {
356
+ program.setUniformi(`volumeTexture[${component}]`, scalarTexture.getTextureUnit());
357
+ });
358
+ const volumeProperties = actor.getProperties();
359
+ const firstValidInput = model.currentValidInputs[0];
360
+ const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex];
361
+ const ipScalarRange = firstVolumeProperty.getIpScalarRange();
362
+ const minVals = new Float32Array(4);
363
+ const maxVals = new Float32Array(4);
364
+ const setMinMaxVal = (component, volInfo, volInfoIndex) => {
365
+ // In some situations, we might not have computed the scale and offset
366
+ // for the data range, or it might not be needed.
367
+ if (volInfo?.dataComputedScale?.length) {
425
368
  // convert iprange from 0-1 into data range values
426
- minVals[i] = ipScalarRange[0] * volInfo.dataComputedScale[i] + volInfo.dataComputedOffset[i];
427
- maxVals[i] = ipScalarRange[1] * volInfo.dataComputedScale[i] + volInfo.dataComputedOffset[i];
369
+ minVals[component] = ipScalarRange[0] * volInfo.dataComputedScale[volInfoIndex] + volInfo.dataComputedOffset[volInfoIndex];
370
+ maxVals[component] = ipScalarRange[1] * volInfo.dataComputedScale[volInfoIndex] + volInfo.dataComputedOffset[volInfoIndex];
428
371
  // convert data ranges into texture values
429
- minVals[i] = (minVals[i] - volInfo.offset[i]) / volInfo.scale[i];
430
- maxVals[i] = (maxVals[i] - volInfo.offset[i]) / volInfo.scale[i];
372
+ minVals[component] = (minVals[component] - volInfo.offset[volInfoIndex]) / volInfo.scale[volInfoIndex];
373
+ maxVals[component] = (maxVals[component] - volInfo.offset[volInfoIndex]) / volInfo.scale[volInfoIndex];
374
+ }
375
+ };
376
+ if (model.previousState.multiTexturePerVolumeEnabled) {
377
+ // Use the first component of all texture infos
378
+ model.scalarTextures.forEach((scalarTexture, component) => {
379
+ const volInfo = scalarTexture.getVolumeInfo();
380
+ setMinMaxVal(component, volInfo, 0);
381
+ });
382
+ } else {
383
+ // Use all components of the first texture info
384
+ const firstVolInfo = model.scalarTextures[0].getVolumeInfo();
385
+ for (let component = 0; component < 4; ++component) {
386
+ setMinMaxVal(component, firstVolInfo, component);
431
387
  }
432
- program.setUniform4f('ipScalarRangeMin', minVals[0], minVals[1], minVals[2], minVals[3]);
433
- program.setUniform4f('ipScalarRangeMax', maxVals[0], maxVals[1], maxVals[2], maxVals[3]);
434
388
  }
389
+ const uniformPrefix = 'volume';
390
+ program.setUniform4f(`${uniformPrefix}.ipScalarRangeMin`, minVals[0], minVals[1], minVals[2], minVals[3]);
391
+ program.setUniform4f(`${uniformPrefix}.ipScalarRangeMax`, maxVals[0], maxVals[1], maxVals[2], maxVals[3]);
435
392
 
436
393
  // if we have a zbuffer texture then set it
437
394
  if (model.zBufferTexture !== null) {
@@ -442,140 +399,174 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
442
399
  }
443
400
  };
444
401
  publicAPI.setCameraShaderParameters = (cellBO, ren, actor) => {
445
- // // [WMVP]C == {world, model, view, projection} coordinates
446
- // // E.g., WCPC == world to projection coordinate transformation
402
+ // These matrices are not cached for their content, but only to avoid reallocations
403
+ const {
404
+ idxToView,
405
+ vecISToVCMatrix,
406
+ modelToView,
407
+ projectionToView,
408
+ projectionToWorld
409
+ } = preAllocatedMatrices;
410
+
411
+ // [WMVP]C == {world, model, view, projection} coordinates
412
+ // E.g., WCPC == world to projection coordinate transformation
447
413
  const keyMats = model.openGLCamera.getKeyMatrices(ren);
448
414
  const actMats = model.openGLVolume.getKeyMatrices();
449
- mat4.multiply(model.modelToView, keyMats.wcvc, actMats.mcwc);
415
+ mat4.multiply(modelToView, keyMats.wcvc, actMats.mcwc);
450
416
  const program = cellBO.getProgram();
451
- const cam = model.openGLCamera.getRenderable();
452
- const crange = cam.getClippingRange();
453
- program.setUniformf('camThick', crange[1] - crange[0]);
454
- program.setUniformf('camNear', crange[0]);
455
- program.setUniformf('camFar', crange[1]);
456
- const bounds = model.currentInput.getBounds();
457
- const dims = model.currentInput.getDimensions();
458
-
459
- // compute the viewport bounds of the volume
460
- // we will only render those fragments.
461
- const pos = new Float64Array(3);
462
- const dir = new Float64Array(3);
463
- let dcxmin = 1.0;
464
- let dcxmax = -1.0;
465
- let dcymin = 1.0;
466
- let dcymax = -1.0;
467
- for (let i = 0; i < 8; ++i) {
468
- vec3.set(pos, bounds[i % 2], bounds[2 + Math.floor(i / 2) % 2], bounds[4 + Math.floor(i / 4)]);
469
- vec3.transformMat4(pos, pos, model.modelToView);
470
- if (!cam.getParallelProjection()) {
471
- vec3.normalize(dir, pos);
472
-
473
- // now find the projection of this point onto a
474
- // nearZ distance plane. Since the camera is at 0,0,0
475
- // in VC the ray is just t*pos and
476
- // t is -nearZ/dir.z
477
- // intersection becomes pos.x/pos.z
478
- const t = -crange[0] / pos[2];
479
- vec3.scale(pos, dir, t);
417
+ const camera = model.openGLCamera.getRenderable();
418
+ const useParallelProjection = camera.getParallelProjection();
419
+ const clippingRange = camera.getClippingRange();
420
+ program.setUniformf('camThick', clippingRange[1] - clippingRange[0]);
421
+ program.setUniformf('camNear', clippingRange[0]);
422
+ program.setUniformf('camFar', clippingRange[1]);
423
+ program.setUniformi('cameraParallel', useParallelProjection);
424
+
425
+ // Compute the viewport bounds of the volume
426
+ // We will only render those fragments
427
+ // First, merge all bounds to get a fusion of all bounds in model coordinates
428
+ const firstValidInput = model.currentValidInputs[0];
429
+ const boundsMC = firstValidInput.imageData.getBounds();
430
+ const cornersMC = vtkBoundingBox.getCorners(boundsMC, []);
431
+ const cornersDC = cornersMC.map(corner => {
432
+ // Convert to view coordinates
433
+ vec3.transformMat4(corner, corner, modelToView);
434
+ if (!useParallelProjection) {
435
+ // Now find the projection of this point onto a
436
+ // nearZ distance plane. Since pos is in view coordinates,
437
+ // scale it until pos.z == nearZ
438
+ const newScale = -clippingRange[0] / (corner[2] * vec3.length(corner));
439
+ vec3.scale(corner, corner, newScale);
480
440
  }
481
- // now convert to DC
482
- vec3.transformMat4(pos, pos, keyMats.vcpc);
483
- dcxmin = Math.min(pos[0], dcxmin);
484
- dcxmax = Math.max(pos[0], dcxmax);
485
- dcymin = Math.min(pos[1], dcymin);
486
- dcymax = Math.max(pos[1], dcymax);
487
- }
488
- program.setUniformf('dcxmin', dcxmin);
489
- program.setUniformf('dcxmax', dcxmax);
490
- program.setUniformf('dcymin', dcymin);
491
- program.setUniformf('dcymax', dcymax);
492
- if (program.isUniformUsed('cameraParallel')) {
493
- program.setUniformi('cameraParallel', cam.getParallelProjection());
494
- }
495
- const ext = model.currentInput.getSpatialExtent();
496
- const spc = model.currentInput.getSpacing();
497
- const vsize = new Float64Array(3);
498
- vec3.set(vsize, (ext[1] - ext[0]) * spc[0], (ext[3] - ext[2]) * spc[1], (ext[5] - ext[4]) * spc[2]);
499
- program.setUniform3f('vSpacing', spc[0], spc[1], spc[2]);
500
- vec3.set(pos, ext[0], ext[2], ext[4]);
501
- model.currentInput.indexToWorldVec3(pos, pos);
502
- vec3.transformMat4(pos, pos, model.modelToView);
503
- program.setUniform3f('vOriginVC', pos[0], pos[1], pos[2]);
504
-
505
- // apply the image directions
506
- const i2wmat4 = model.currentInput.getIndexToWorld();
507
- mat4.multiply(model.idxToView, model.modelToView, i2wmat4);
508
- mat3.multiply(model.idxNormalMatrix, keyMats.normalMatrix, actMats.normalMatrix);
509
- mat3.multiply(model.idxNormalMatrix, model.idxNormalMatrix, model.currentInput.getDirectionByReference());
510
- const maxSamples = vec3.length(vsize) / publicAPI.getCurrentSampleDistance(ren);
511
- if (maxSamples > model.renderable.getMaximumSamplesPerRay()) {
512
- vtkWarningMacro(`The number of steps required ${Math.ceil(maxSamples)} is larger than the
513
- specified maximum number of steps ${model.renderable.getMaximumSamplesPerRay()}.
514
- Please either change the
515
- volumeMapper sampleDistance or its maximum number of samples.`);
516
- }
517
- const vctoijk = new Float64Array(3);
518
- vec3.set(vctoijk, 1.0, 1.0, 1.0);
519
- vec3.divide(vctoijk, vctoijk, vsize);
520
- program.setUniform3f('vVCToIJK', vctoijk[0], vctoijk[1], vctoijk[2]);
521
- program.setUniform3i('volumeDimensions', dims[0], dims[1], dims[2]);
522
- program.setUniform3f('volumeSpacings', spc[0], spc[1], spc[2]);
523
- if (!model._openGLRenderWindow.getWebgl2()) {
524
- const volInfo = model.scalarTexture.getVolumeInfo();
525
- program.setUniformf('texWidth', model.scalarTexture.getWidth());
526
- program.setUniformf('texHeight', model.scalarTexture.getHeight());
527
- program.setUniformi('xreps', volInfo.xreps);
528
- program.setUniformi('xstride', volInfo.xstride);
529
- program.setUniformi('ystride', volInfo.ystride);
441
+
442
+ // Now convert to display coordinates
443
+ vec3.transformMat4(corner, corner, keyMats.vcpc);
444
+ return corner;
445
+ });
446
+ const boundsDC = vtkBoundingBox.addPoints([...vtkBoundingBox.INIT_BOUNDS], cornersDC);
447
+ program.setUniformf('dcxmin', boundsDC[0]);
448
+ program.setUniformf('dcxmax', boundsDC[1]);
449
+ program.setUniformf('dcymin', boundsDC[2]);
450
+ program.setUniformf('dcymax', boundsDC[3]);
451
+ const size = publicAPI.getRenderTargetSize();
452
+ program.setUniformf('vpWidth', size[0]);
453
+ program.setUniformf('vpHeight', size[1]);
454
+ const offset = publicAPI.getRenderTargetOffset();
455
+ program.setUniformf('vpOffsetX', offset[0] / size[0]);
456
+ program.setUniformf('vpOffsetY', offset[1] / size[1]);
457
+ mat4.invert(projectionToView, keyMats.vcpc);
458
+ program.setUniformMatrix('PCVCMatrix', projectionToView);
459
+ program.setUniformi('twoSidedLighting', ren.getTwoSidedLighting());
460
+ const kernelSample = new Array(2 * model.previousState.maxLaoKernelSize);
461
+ for (let i = 0; i < model.previousState.maxLaoKernelSize; i++) {
462
+ kernelSample[i * 2] = Math.random();
463
+ kernelSample[i * 2 + 1] = Math.random();
530
464
  }
465
+ program.setUniform2fv('kernelSample', kernelSample);
531
466
 
532
- // map normals through normal matrix
533
- // then use a point on the plane to compute the distance
534
- const normal = new Float64Array(3);
535
- const pos2 = new Float64Array(3);
536
- for (let i = 0; i < 6; ++i) {
537
- switch (i) {
538
- case 1:
539
- vec3.set(normal, -1.0, 0.0, 0.0);
540
- vec3.set(pos2, ext[0], ext[2], ext[4]);
541
- break;
542
- case 2:
543
- vec3.set(normal, 0.0, 1.0, 0.0);
544
- vec3.set(pos2, ext[1], ext[3], ext[5]);
545
- break;
546
- case 3:
547
- vec3.set(normal, 0.0, -1.0, 0.0);
548
- vec3.set(pos2, ext[0], ext[2], ext[4]);
549
- break;
550
- case 4:
551
- vec3.set(normal, 0.0, 0.0, 1.0);
552
- vec3.set(pos2, ext[1], ext[3], ext[5]);
553
- break;
554
- case 5:
555
- vec3.set(normal, 0.0, 0.0, -1.0);
556
- vec3.set(pos2, ext[0], ext[2], ext[4]);
557
- break;
558
- case 0:
559
- default:
560
- vec3.set(normal, 1.0, 0.0, 0.0);
561
- vec3.set(pos2, ext[1], ext[3], ext[5]);
562
- break;
563
- }
564
- vec3.transformMat3(normal, normal, model.idxNormalMatrix);
565
- vec3.transformMat4(pos2, pos2, model.idxToView);
566
- const dist = -1.0 * vec3.dot(pos2, normal);
567
-
568
- // we have the plane in view coordinates
569
- // specify the planes in view coordinates
570
- program.setUniform3f(`vPlaneNormal${i}`, normal[0], normal[1], normal[2]);
571
- program.setUniformf(`vPlaneDistance${i}`, dist);
467
+ // Handle lighting values
468
+ if (model.numberOfLights > 0) {
469
+ let lightIndex = 0;
470
+ ren.getLights().forEach(light => {
471
+ if (light.getSwitch() > 0) {
472
+ const lightPrefix = `lights[${lightIndex}]`;
473
+
474
+ // Merge color and intensity
475
+ const color = light.getColor();
476
+ const intensity = light.getIntensity();
477
+ const scaledColor = vec3.scale([], color, intensity);
478
+ program.setUniform3fv(`${lightPrefix}.color`, scaledColor);
479
+
480
+ // Position in view coordinates
481
+ const position = light.getTransformedPosition();
482
+ vec3.transformMat4(position, position, modelToView);
483
+ program.setUniform3fv(`${lightPrefix}.positionVC`, position);
484
+
485
+ // Convert lightDirection in view coordinates and normalize it
486
+ const direction = [...light.getDirection()];
487
+ vec3.transformMat3(direction, direction, keyMats.normalMatrix);
488
+ vec3.normalize(direction, direction);
489
+ program.setUniform3fv(`${lightPrefix}.directionVC`, direction);
490
+
491
+ // Camera direction of projection is (0, 0, -1.0) in view coordinates
492
+ const halfAngle = [-0.5 * direction[0], -0.5 * direction[1], -0.5 * (direction[2] - 1.0)];
493
+ program.setUniform3fv(`${lightPrefix}.halfAngleVC`, halfAngle);
494
+
495
+ // Attenuation
496
+ const attenuation = light.getAttenuationValues();
497
+ program.setUniform3fv(`${lightPrefix}.attenuation`, attenuation);
498
+
499
+ // Exponent
500
+ const exponent = light.getExponent();
501
+ program.setUniformf(`${lightPrefix}.exponent`, exponent);
502
+
503
+ // Cone angle
504
+ const coneAngle = light.getConeAngle();
505
+ program.setUniformf(`${lightPrefix}.coneAngle`, coneAngle);
506
+
507
+ // Positional flag
508
+ const isPositional = light.getPositional();
509
+ program.setUniformi(`${lightPrefix}.isPositional`, isPositional);
510
+ lightIndex++;
511
+ }
512
+ });
572
513
  }
573
- if (publicAPI.isLabelmapOutlineRequired(actor)) {
574
- const image = model.currentInput;
575
- const worldToIndex = image.getWorldToIndex();
576
- program.setUniformMatrix('vWCtoIDX', worldToIndex);
577
- const camera = ren.getActiveCamera();
578
- const [cRange0, cRange1] = camera.getClippingRange();
514
+
515
+ // Set uniforms for the volume
516
+ const uniformPrefix = 'volume';
517
+ const volumeProperties = actor.getProperties();
518
+ const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex];
519
+ const firstImageData = firstValidInput.imageData;
520
+ const spatialExtent = firstImageData.getSpatialExtent();
521
+ const spacing = firstImageData.getSpacing();
522
+ const dimensions = firstImageData.getDimensions();
523
+ const idxToModel = firstImageData.getIndexToWorld();
524
+ const worldToIndex = firstImageData.getWorldToIndex();
525
+ const imageDirection = firstImageData.getDirectionByReference();
526
+
527
+ // idxToView is equivalent to applying idxToModel then modelToView
528
+ mat4.multiply(idxToView, modelToView, idxToModel);
529
+
530
+ // Set spacing uniform
531
+ program.setUniform3fv(`${uniformPrefix}.spacing`, spacing);
532
+ const inverseSpacing = vec3.inverse([], spacing);
533
+ program.setUniform3fv(`${uniformPrefix}.inverseSpacing`, inverseSpacing);
534
+
535
+ // Set dimensions uniform
536
+ program.setUniform3iv(`${uniformPrefix}.dimensions`, dimensions);
537
+
538
+ // Set inverse dimensions uniform
539
+ program.setUniform3fv(`${uniformPrefix}.inverseDimensions`, vec3.inverse([], dimensions));
540
+
541
+ // Set world to index
542
+ program.setUniformMatrix(`${uniformPrefix}.worldToIndex`, worldToIndex);
543
+
544
+ // Create the vecISToVCMatrix, that transform a point from texture coordinates (IS in the shader) to VC coordinates
545
+ vecISToVCMatrix.fill(0);
546
+ // First apply scaling
547
+ // mat3.fromScaling can't be used, because it uses a vec2 for scaling
548
+ const sizeVC = vec3.multiply(new Float64Array(3), dimensions, spacing);
549
+ vecISToVCMatrix[0] = sizeVC[0];
550
+ vecISToVCMatrix[4] = sizeVC[1];
551
+ vecISToVCMatrix[8] = sizeVC[2];
552
+ // Then apply the image direction matrix
553
+ mat3.multiply(vecISToVCMatrix, imageDirection, vecISToVCMatrix);
554
+ // Then apply the actor matrix
555
+ mat3.multiply(vecISToVCMatrix, actMats.normalMatrix, vecISToVCMatrix);
556
+ // Then apply the camera matrix
557
+ mat3.multiply(vecISToVCMatrix, keyMats.normalMatrix, vecISToVCMatrix);
558
+ program.setUniformMatrix3x3(`${uniformPrefix}.vecISToVCMatrix`, vecISToVCMatrix);
559
+ program.setUniformMatrix3x3(`${uniformPrefix}.vecVCToISMatrix`, mat3.invert(new Float32Array(9), vecISToVCMatrix));
560
+
561
+ // Set originVC uniform that will be used to convert points from IS to VC
562
+ // It will be done in this way: posVC = vecISToVCMatrix * posIS + originVC
563
+ // Or the other way around: posIS = vecVCtoISMatrix * (posVC - originVC)
564
+ const spacialExtentMinIC = vec3.fromValues(spatialExtent[0], spatialExtent[2], spatialExtent[4]);
565
+ const originVC = vec3.transformMat4(new Float64Array(3), spacialExtentMinIC, idxToView);
566
+ program.setUniform3fv(`${uniformPrefix}.originVC`, originVC);
567
+ const diagonalLength = vec3.length(sizeVC);
568
+ program.setUniformf(`${uniformPrefix}.diagonalLength`, diagonalLength);
569
+ if (isLabelmapOutlineRequired(firstVolumeProperty)) {
579
570
  const distance = camera.getDistance();
580
571
 
581
572
  // set the clipping range to be model.distance and model.distance + 0.1
@@ -588,186 +579,151 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
588
579
  const labelOutlineKeyMats = model.openGLCamera.getKeyMatrices(ren);
589
580
 
590
581
  // Get the projection coordinate to world coordinate transformation matrix.
591
- mat4.invert(model.projectionToWorld, labelOutlineKeyMats.wcpc);
582
+ mat4.invert(projectionToWorld, labelOutlineKeyMats.wcpc);
592
583
 
593
584
  // reset the clipping range since the keyMats are cached
594
- camera.setClippingRange(cRange0, cRange1);
585
+ camera.setClippingRange(clippingRange[0], clippingRange[1]);
595
586
 
596
587
  // to re compute the matrices for the current camera and cache them
597
588
  model.openGLCamera.getKeyMatrices(ren);
598
- program.setUniformMatrix('PCWCMatrix', model.projectionToWorld);
599
- const size = publicAPI.getRenderTargetSize();
600
- program.setUniformf('vpWidth', size[0]);
601
- program.setUniformf('vpHeight', size[1]);
602
- const offset = publicAPI.getRenderTargetOffset();
603
- program.setUniformf('vpOffsetX', offset[0] / size[0]);
604
- program.setUniformf('vpOffsetY', offset[1] / size[1]);
605
- }
606
- mat4.invert(model.projectionToView, keyMats.vcpc);
607
- program.setUniformMatrix('PCVCMatrix', model.projectionToView);
608
-
609
- // handle lighting values
610
- if (model.lightComplexity === 0) {
611
- return;
612
- }
613
- let lightNum = 0;
614
- const lightColor = [];
615
- const lightDir = [];
616
- const halfAngle = [];
617
- ren.getLights().forEach(light => {
618
- const status = light.getSwitch();
619
- if (status > 0) {
620
- const dColor = light.getColor();
621
- const intensity = light.getIntensity();
622
- lightColor[0 + lightNum * 3] = dColor[0] * intensity;
623
- lightColor[1 + lightNum * 3] = dColor[1] * intensity;
624
- lightColor[2 + lightNum * 3] = dColor[2] * intensity;
625
- const ldir = light.getDirection();
626
- vec3.set(normal, ldir[0], ldir[1], ldir[2]);
627
- vec3.transformMat3(normal, normal, keyMats.normalMatrix); // in view coordinat
628
- vec3.normalize(normal, normal);
629
- lightDir[0 + lightNum * 3] = normal[0];
630
- lightDir[1 + lightNum * 3] = normal[1];
631
- lightDir[2 + lightNum * 3] = normal[2];
632
- // camera DOP is 0,0,-1.0 in VC
633
- halfAngle[0 + lightNum * 3] = -0.5 * normal[0];
634
- halfAngle[1 + lightNum * 3] = -0.5 * normal[1];
635
- halfAngle[2 + lightNum * 3] = -0.5 * (normal[2] - 1.0);
636
- lightNum++;
637
- }
638
- });
639
- program.setUniformi('twoSidedLighting', ren.getTwoSidedLighting());
640
- program.setUniformi('lightNum', lightNum);
641
- program.setUniform3fv('lightColor', lightColor);
642
- program.setUniform3fv('lightDirectionVC', lightDir);
643
- program.setUniform3fv('lightHalfAngleVC', halfAngle);
644
- if (model.lightComplexity === 3) {
645
- lightNum = 0;
646
- const lightPositionVC = [];
647
- const lightAttenuation = [];
648
- const lightConeAngle = [];
649
- const lightExponent = [];
650
- const lightPositional = [];
651
- ren.getLights().forEach(light => {
652
- const status = light.getSwitch();
653
- if (status > 0) {
654
- const attenuation = light.getAttenuationValues();
655
- lightAttenuation[0 + lightNum * 3] = attenuation[0];
656
- lightAttenuation[1 + lightNum * 3] = attenuation[1];
657
- lightAttenuation[2 + lightNum * 3] = attenuation[2];
658
- lightExponent[lightNum] = light.getExponent();
659
- lightConeAngle[lightNum] = light.getConeAngle();
660
- lightPositional[lightNum] = light.getPositional();
661
- const lp = light.getTransformedPosition();
662
- vec3.transformMat4(lp, lp, model.modelToView);
663
- lightPositionVC[0 + lightNum * 3] = lp[0];
664
- lightPositionVC[1 + lightNum * 3] = lp[1];
665
- lightPositionVC[2 + lightNum * 3] = lp[2];
666
- lightNum += 1;
667
- }
668
- });
669
- program.setUniform3fv('lightPositionVC', lightPositionVC);
670
- program.setUniform3fv('lightAttenuation', lightAttenuation);
671
- program.setUniformfv('lightConeAngle', lightConeAngle);
672
- program.setUniformfv('lightExponent', lightExponent);
673
- program.setUniformiv('lightPositional', lightPositional);
674
- }
675
- if (model.renderable.getVolumetricScatteringBlending() > 0.0) {
676
- program.setUniformf('giReach', model.renderable.getGlobalIlluminationReach());
677
- program.setUniformf('volumetricScatteringBlending', model.renderable.getVolumetricScatteringBlending());
678
- program.setUniformf('volumeShadowSamplingDistFactor', model.renderable.getVolumeShadowSamplingDistFactor());
679
- program.setUniformf('anisotropy', model.renderable.getAnisotropy());
680
- program.setUniformf('anisotropy2', model.renderable.getAnisotropy() ** 2.0);
681
- }
682
- if (model.renderable.getLocalAmbientOcclusion() && actor.getProperty().getAmbient() > 0.0) {
683
- const ks = model.renderable.getLAOKernelSize();
684
- program.setUniformi('kernelSize', ks);
685
- const kernelSample = [];
686
- for (let i = 0; i < ks; i++) {
687
- kernelSample[i * 2] = Math.random() * 0.5;
688
- kernelSample[i * 2 + 1] = Math.random() * 0.5;
689
- }
690
- program.setUniform2fv('kernelSample', kernelSample);
691
- program.setUniformi('kernelRadius', model.renderable.getLAOKernelRadius());
589
+ program.setUniformMatrix(`${uniformPrefix}.PCWCMatrix`, projectionToWorld);
590
+ }
591
+ if (firstVolumeProperty.getVolumetricScatteringBlending() > 0.0) {
592
+ program.setUniformf(`${uniformPrefix}.globalIlluminationReach`, firstVolumeProperty.getGlobalIlluminationReach());
593
+ program.setUniformf(`${uniformPrefix}.volumetricScatteringBlending`, firstVolumeProperty.getVolumetricScatteringBlending());
594
+ program.setUniformf(`${uniformPrefix}.anisotropy`, firstVolumeProperty.getAnisotropy());
595
+ program.setUniformf(`${uniformPrefix}.anisotropySquared`, firstVolumeProperty.getAnisotropy() ** 2.0);
596
+ }
597
+ if (firstVolumeProperty.getLocalAmbientOcclusion() && firstVolumeProperty.getAmbient() > 0.0) {
598
+ const kernelSize = firstVolumeProperty.getLAOKernelSize();
599
+ program.setUniformi(`${uniformPrefix}.kernelSize`, kernelSize);
600
+ const kernelRadius = firstVolumeProperty.getLAOKernelRadius();
601
+ program.setUniformi(`${uniformPrefix}.kernelRadius`, kernelRadius);
602
+ } else {
603
+ program.setUniformi(`${uniformPrefix}.kernelSize`, 0);
692
604
  }
693
605
  };
694
606
  publicAPI.setPropertyShaderParameters = (cellBO, ren, actor) => {
695
607
  const program = cellBO.getProgram();
696
- program.setUniformi('ctexture', model.colorTexture.getTextureUnit());
697
- program.setUniformi('otexture', model.opacityTexture.getTextureUnit());
698
608
  program.setUniformi('jtexture', model.jitterTexture.getTextureUnit());
699
- program.setUniformi('ttexture', model.labelOutlineThicknessTexture.getTextureUnit());
700
- const volInfo = model.scalarTexture.getVolumeInfo();
701
- const vprop = actor.getProperty();
609
+ const volumeProperties = actor.getProperties();
610
+
611
+ // There is only one label outline thickness texture
612
+ program.setUniformi(`labelOutlineThicknessTexture`, model.labelOutlineThicknessTexture.getTextureUnit());
613
+ program.setUniformi('opacityTexture', model.opacityTexture.getTextureUnit());
614
+ program.setUniformi('colorTexture', model.colorTexture.getTextureUnit());
615
+ const uniformPrefix = 'volume';
616
+ const firstValidInput = model.currentValidInputs[0];
617
+ const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex];
618
+ const numberOfComponents = model.previousState.numberOfComponents;
619
+ const useIndependentComponents = model.previousState.useIndependentComponents;
702
620
 
703
621
  // set the component mix when independent
704
- const numComp = model.scalarTexture.getComponents();
705
- const useIndependentComps = publicAPI.useIndependentComponents(vprop);
706
- if (useIndependentComps) {
707
- for (let i = 0; i < numComp; i++) {
708
- program.setUniformf(`mix${i}`, actor.getProperty().getComponentWeight(i));
622
+ if (useIndependentComponents) {
623
+ const independentComponentMix = new Float32Array(4);
624
+ for (let i = 0; i < numberOfComponents; i++) {
625
+ independentComponentMix[i] = firstVolumeProperty.getComponentWeight(i);
626
+ }
627
+ program.setUniform4fv(`${uniformPrefix}.independentComponentMix`, independentComponentMix);
628
+ const transferFunctionsSampleHeight = new Float32Array(4);
629
+ const pixelHeight = 1 / numberOfComponents;
630
+ for (let i = 0; i < numberOfComponents; ++i) {
631
+ transferFunctionsSampleHeight[i] = (i + 0.5) * pixelHeight;
709
632
  }
633
+ program.setUniform4fv(`${uniformPrefix}.transferFunctionsSampleHeight`, transferFunctionsSampleHeight);
710
634
  }
635
+ const colorForValueFunctionId = model.colorForValueFunctionId;
636
+ program.setUniformi(`${uniformPrefix}.colorForValueFunctionId`, colorForValueFunctionId);
637
+ const computeNormalFromOpacity = firstVolumeProperty.getComputeNormalFromOpacity();
638
+ program.setUniformi(`${uniformPrefix}.computeNormalFromOpacity`, computeNormalFromOpacity);
711
639
 
712
640
  // three levels of shift scale combined into one
713
641
  // for performance in the fragment shader
714
- for (let i = 0; i < numComp; i++) {
715
- const target = useIndependentComps ? i : 0;
716
- const sscale = volInfo.scale[i];
717
- const ofun = vprop.getScalarOpacity(target);
718
- const oRange = ofun.getRange();
719
- const oscale = sscale / (oRange[1] - oRange[0]);
720
- const oshift = (volInfo.offset[i] - oRange[0]) / (oRange[1] - oRange[0]);
721
- program.setUniformf(`oshift${i}`, oshift);
722
- program.setUniformf(`oscale${i}`, oscale);
723
- const cfun = vprop.getRGBTransferFunction(target);
724
- const cRange = cfun.getRange();
725
- const cshift = (volInfo.offset[i] - cRange[0]) / (cRange[1] - cRange[0]);
726
- const cScale = sscale / (cRange[1] - cRange[0]);
727
- program.setUniformf(`cshift${i}`, cshift);
728
- program.setUniformf(`cscale${i}`, cScale);
729
- }
730
- if (model.gopacity) {
731
- if (useIndependentComps) {
732
- for (let nc = 0; nc < numComp; ++nc) {
733
- const sscale = volInfo.scale[nc];
734
- const useGO = vprop.getUseGradientOpacity(nc);
642
+ const colorTextureScale = new Float32Array(4);
643
+ const colorTextureShift = new Float32Array(4);
644
+ const opacityTextureScale = new Float32Array(4);
645
+ const opacityTextureShift = new Float32Array(4);
646
+ for (let component = 0; component < numberOfComponents; component++) {
647
+ const useMultiTexture = model.previousState.multiTexturePerVolumeEnabled;
648
+ const textureIndex = useMultiTexture ? component : 0;
649
+ const volInfoIndex = useMultiTexture ? 0 : component;
650
+ const scalarTexture = model.scalarTextures[textureIndex];
651
+ const volInfo = scalarTexture.getVolumeInfo();
652
+ const target = useIndependentComponents ? component : 0;
653
+ const sscale = volInfo.scale[volInfoIndex];
654
+
655
+ // Color
656
+ const colorFunction = firstVolumeProperty.getRGBTransferFunction(target);
657
+ const colorRange = colorFunction.getRange();
658
+ colorTextureScale[component] = sscale / (colorRange[1] - colorRange[0]);
659
+ colorTextureShift[component] = (volInfo.offset[volInfoIndex] - colorRange[0]) / (colorRange[1] - colorRange[0]);
660
+
661
+ // Opacity
662
+ const opacityFunction = firstVolumeProperty.getScalarOpacity(target);
663
+ const opacityRange = opacityFunction.getRange();
664
+ opacityTextureScale[component] = sscale / (opacityRange[1] - opacityRange[0]);
665
+ opacityTextureShift[component] = (volInfo.offset[volInfoIndex] - opacityRange[0]) / (opacityRange[1] - opacityRange[0]);
666
+ }
667
+ program.setUniform4fv(`${uniformPrefix}.colorTextureScale`, colorTextureScale);
668
+ program.setUniform4fv(`${uniformPrefix}.colorTextureShift`, colorTextureShift);
669
+ program.setUniform4fv(`${uniformPrefix}.opacityTextureScale`, opacityTextureScale);
670
+ program.setUniform4fv(`${uniformPrefix}.opacityTextureShift`, opacityTextureShift);
671
+ if (model.previousState.gradientOpacityEnabled) {
672
+ const gradientOpacityScale = new Array(4);
673
+ const gradientOpacityShift = new Array(4);
674
+ const gradientOpacityMin = new Array(4);
675
+ const gradientOpacityMax = new Array(4);
676
+ if (useIndependentComponents) {
677
+ for (let component = 0; component < numberOfComponents; ++component) {
678
+ const useMultiTexture = model.previousState.multiTexturePerVolumeEnabled;
679
+ const textureIndex = useMultiTexture ? component : 0;
680
+ const volInfoIndex = useMultiTexture ? 0 : component;
681
+ const scalarTexture = model.scalarTextures[textureIndex];
682
+ const volInfo = scalarTexture.getVolumeInfo();
683
+ const sscale = volInfo.scale[volInfoIndex];
684
+ const useGO = firstVolumeProperty.getUseGradientOpacity(component);
735
685
  if (useGO) {
736
- const gomin = vprop.getGradientOpacityMinimumOpacity(nc);
737
- const gomax = vprop.getGradientOpacityMaximumOpacity(nc);
738
- program.setUniformf(`gomin${nc}`, gomin);
739
- program.setUniformf(`gomax${nc}`, gomax);
740
- const goRange = [vprop.getGradientOpacityMinimumValue(nc), vprop.getGradientOpacityMaximumValue(nc)];
741
- program.setUniformf(`goscale${nc}`, sscale * (gomax - gomin) / (goRange[1] - goRange[0]));
742
- program.setUniformf(`goshift${nc}`, -goRange[0] * (gomax - gomin) / (goRange[1] - goRange[0]) + gomin);
686
+ const goOpacityRange = [firstVolumeProperty.getGradientOpacityMinimumOpacity(component), firstVolumeProperty.getGradientOpacityMaximumOpacity(component)];
687
+ const goValueRange = [firstVolumeProperty.getGradientOpacityMinimumValue(component), firstVolumeProperty.getGradientOpacityMaximumValue(component)];
688
+ gradientOpacityMin[component] = goOpacityRange[0];
689
+ gradientOpacityMax[component] = goOpacityRange[1];
690
+ gradientOpacityScale[component] = sscale * (goOpacityRange[1] - goOpacityRange[0]) / (goValueRange[1] - goValueRange[0]);
691
+ gradientOpacityShift[component] = -goValueRange[0] * (goOpacityRange[1] - goOpacityRange[0]) / (goValueRange[1] - goValueRange[0]) + goOpacityRange[0];
743
692
  } else {
744
- program.setUniformf(`gomin${nc}`, 1.0);
745
- program.setUniformf(`gomax${nc}`, 1.0);
746
- program.setUniformf(`goscale${nc}`, 0.0);
747
- program.setUniformf(`goshift${nc}`, 1.0);
693
+ gradientOpacityMin[component] = 1;
694
+ gradientOpacityMax[component] = 1;
695
+ gradientOpacityScale[component] = 0;
696
+ gradientOpacityShift[component] = 1;
748
697
  }
749
698
  }
750
699
  } else {
751
- const sscale = volInfo.scale[numComp - 1];
752
- const gomin = vprop.getGradientOpacityMinimumOpacity(0);
753
- const gomax = vprop.getGradientOpacityMaximumOpacity(0);
754
- program.setUniformf('gomin0', gomin);
755
- program.setUniformf('gomax0', gomax);
756
- const goRange = [vprop.getGradientOpacityMinimumValue(0), vprop.getGradientOpacityMaximumValue(0)];
757
- program.setUniformf('goscale0', sscale * (gomax - gomin) / (goRange[1] - goRange[0]));
758
- program.setUniformf('goshift0', -goRange[0] * (gomax - gomin) / (goRange[1] - goRange[0]) + gomin);
700
+ const component = numberOfComponents - 1;
701
+ const useMultiTexture = model.previousState.multiTexturePerVolumeEnabled;
702
+ const textureIndex = useMultiTexture ? component : 0;
703
+ const volInfoIndex = useMultiTexture ? 0 : component;
704
+ const scalarTexture = model.scalarTextures[textureIndex];
705
+ const volInfo = scalarTexture.getVolumeInfo();
706
+ const sscale = volInfo.scale[volInfoIndex];
707
+ const goOpacityRange = [firstVolumeProperty.getGradientOpacityMinimumOpacity(0), firstVolumeProperty.getGradientOpacityMaximumOpacity(0)];
708
+ const goValueRange = [firstVolumeProperty.getGradientOpacityMinimumValue(0), firstVolumeProperty.getGradientOpacityMaximumValue(0)];
709
+ gradientOpacityMin[0] = goOpacityRange[0];
710
+ gradientOpacityMax[0] = goOpacityRange[1];
711
+ gradientOpacityScale[0] = sscale * (goOpacityRange[1] - goOpacityRange[0]) / (goValueRange[1] - goValueRange[0]);
712
+ gradientOpacityShift[0] = -goValueRange[0] * (goOpacityRange[1] - goOpacityRange[0]) / (goValueRange[1] - goValueRange[0]) + goOpacityRange[0];
759
713
  }
760
- }
761
- const vtkImageLabelOutline = publicAPI.isLabelmapOutlineRequired(actor);
762
- if (vtkImageLabelOutline === true) {
763
- const labelOutlineOpacity = actor.getProperty().getLabelOutlineOpacity();
764
- program.setUniformf('outlineOpacity', labelOutlineOpacity);
765
- }
766
- if (model.lightComplexity > 0) {
767
- program.setUniformf('vAmbient', vprop.getAmbient());
768
- program.setUniformf('vDiffuse', vprop.getDiffuse());
769
- program.setUniformf('vSpecular', vprop.getSpecular());
770
- program.setUniformf('vSpecularPower', vprop.getSpecularPower());
714
+ program.setUniform4f(`${uniformPrefix}.gradientOpacityScale`, gradientOpacityScale);
715
+ program.setUniform4f(`${uniformPrefix}.gradientOpacityShift`, gradientOpacityShift);
716
+ program.setUniform4f(`${uniformPrefix}.gradientOpacityMin`, gradientOpacityMin);
717
+ program.setUniform4f(`${uniformPrefix}.gradientOpacityMax`, gradientOpacityMax);
718
+ }
719
+ const outlineOpacity = firstVolumeProperty.getLabelOutlineOpacity();
720
+ program.setUniformf(`${uniformPrefix}.outlineOpacity`, outlineOpacity);
721
+ if (model.numberOfLights > 0) {
722
+ program.setUniformf(`${uniformPrefix}.ambient`, firstVolumeProperty.getAmbient());
723
+ program.setUniformf(`${uniformPrefix}.diffuse`, firstVolumeProperty.getDiffuse());
724
+ program.setUniformf(`${uniformPrefix}.specular`, firstVolumeProperty.getSpecular());
725
+ const specularPower = firstVolumeProperty.getSpecularPower();
726
+ program.setUniformf(`${uniformPrefix}.specularPower`, specularPower === 0 ? 1.0 : specularPower);
771
727
  }
772
728
  };
773
729
  publicAPI.getClippingPlaneShaderParameters = (cellBO, ren, actor) => {
@@ -911,14 +867,22 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
911
867
  publicAPI.updateBufferObjects(ren, actor);
912
868
 
913
869
  // set interpolation on the texture based on property setting
914
- const iType = actor.getProperty().getInterpolationType();
915
- if (iType === InterpolationType.NEAREST) {
916
- model.scalarTexture.setMinificationFilter(Filter.NEAREST);
917
- model.scalarTexture.setMagnificationFilter(Filter.NEAREST);
918
- } else {
919
- model.scalarTexture.setMinificationFilter(Filter.LINEAR);
920
- model.scalarTexture.setMagnificationFilter(Filter.LINEAR);
921
- }
870
+ const volumeProperties = actor.getProperties();
871
+ model.currentValidInputs.forEach(_ref => {
872
+ let {
873
+ inputIndex
874
+ } = _ref;
875
+ const volumeProperty = volumeProperties[inputIndex];
876
+ const interpolationType = volumeProperty.getInterpolationType();
877
+ const scalarTexture = model.scalarTextures[inputIndex];
878
+ if (interpolationType === InterpolationType.NEAREST) {
879
+ scalarTexture.setMinificationFilter(Filter.NEAREST);
880
+ scalarTexture.setMagnificationFilter(Filter.NEAREST);
881
+ } else {
882
+ scalarTexture.setMinificationFilter(Filter.LINEAR);
883
+ scalarTexture.setMagnificationFilter(Filter.LINEAR);
884
+ }
885
+ });
922
886
 
923
887
  // if we have a zbuffer texture then activate it
924
888
  if (model.zBufferTexture !== null) {
@@ -929,24 +893,14 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
929
893
  const gl = model.context;
930
894
 
931
895
  // render the texture
932
- model.scalarTexture.activate();
933
- model.opacityTexture.activate();
934
- model.labelOutlineThicknessTexture.activate();
935
- model.colorTexture.activate();
936
- model.jitterTexture.activate();
896
+ const allTextures = [...model.scalarTextures, model.colorTexture, model.opacityTexture, model.labelOutlineThicknessTexture, model.jitterTexture];
897
+ allTextures.forEach(texture => texture.activate());
937
898
  publicAPI.updateShaders(model.tris, ren, actor);
938
899
 
939
900
  // First we do the triangles, update the shader, set uniforms, etc.
940
- // for (let i = 0; i < 11; ++i) {
941
- // gl.drawArrays(gl.TRIANGLES, 66 * i, 66);
942
- // }
943
901
  gl.drawArrays(gl.TRIANGLES, 0, model.tris.getCABO().getElementCount());
944
902
  model.tris.getVAO().release();
945
- model.scalarTexture.deactivate();
946
- model.colorTexture.deactivate();
947
- model.opacityTexture.deactivate();
948
- model.labelOutlineThicknessTexture.deactivate();
949
- model.jitterTexture.deactivate();
903
+ allTextures.forEach(texture => texture.deactivate());
950
904
  };
951
905
  publicAPI.renderPieceFinish = (ren, actor) => {
952
906
  // if we have a zbuffer texture then deactivate it
@@ -990,69 +944,102 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
990
944
  publicAPI.invokeEvent({
991
945
  type: 'StartEvent'
992
946
  });
947
+
948
+ // Get the valid image data inputs
993
949
  model.renderable.update();
994
- model.currentInput = model.renderable.getInputData();
950
+ const numberOfInputs = model.renderable.getNumberOfInputPorts();
951
+ model.currentValidInputs = [];
952
+ for (let inputIndex = 0; inputIndex < numberOfInputs; ++inputIndex) {
953
+ const imageData = model.renderable.getInputData(inputIndex);
954
+ if (imageData && !imageData.isDeleted()) {
955
+ model.currentValidInputs.push({
956
+ imageData,
957
+ inputIndex
958
+ });
959
+ }
960
+ }
961
+ const volumeProperties = actor.getProperties();
962
+ const firstValidInput = model.currentValidInputs[0];
963
+ const firstImageData = firstValidInput.imageData;
964
+ const firstScalars = firstImageData.getPointData().getScalars();
965
+ const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex];
966
+
967
+ // Get the number of lights
968
+ let newNumberOfLights = 0;
969
+ if (firstVolumeProperty.getShade() && model.renderable.getBlendMode() === BlendMode.COMPOSITE_BLEND) {
970
+ ren.getLights().forEach(light => {
971
+ if (light.getSwitch() > 0) {
972
+ newNumberOfLights++;
973
+ }
974
+ });
975
+ }
976
+ if (newNumberOfLights !== model.numberOfLights) {
977
+ model.numberOfLights = newNumberOfLights;
978
+ publicAPI.modified();
979
+ }
995
980
  publicAPI.invokeEvent({
996
981
  type: 'EndEvent'
997
982
  });
998
- if (!model.currentInput) {
999
- vtkErrorMacro('No input!');
983
+ if (model.currentValidInputs.length === 0) {
1000
984
  return;
1001
985
  }
986
+
987
+ // Number of components
988
+ const numberOfValidInputs = model.currentValidInputs.length;
989
+ const multiTexturePerVolumeEnabled = numberOfValidInputs > 1;
990
+ model.numberOfComponents = multiTexturePerVolumeEnabled ? numberOfValidInputs : firstScalars.getNumberOfComponents();
991
+ model.useIndependentComponents = getUseIndependentComponents(firstVolumeProperty, model.numberOfComponents);
1002
992
  publicAPI.renderPieceStart(ren, actor);
1003
993
  publicAPI.renderPieceDraw(ren, actor);
1004
994
  publicAPI.renderPieceFinish(ren, actor);
1005
995
  };
1006
- publicAPI.computeBounds = (ren, actor) => {
1007
- if (!publicAPI.getInput()) {
1008
- uninitializeBounds(model.Bounds);
1009
- return;
1010
- }
1011
- model.bounds = publicAPI.getInput().getBounds();
1012
- };
1013
996
  publicAPI.updateBufferObjects = (ren, actor) => {
1014
997
  // Rebuild buffers if needed
1015
998
  if (publicAPI.getNeedToRebuildBufferObjects(ren, actor)) {
1016
999
  publicAPI.buildBufferObjects(ren, actor);
1017
1000
  }
1018
1001
  };
1019
- publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => {
1020
- // first do a coarse check
1021
- if (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.scalarTexture?.getHandle() || !model.colorTexture?.getHandle() || !model.labelOutlineThicknessTexture?.getHandle()) {
1022
- return true;
1023
- }
1024
- return false;
1025
- };
1002
+ publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => model.VBOBuildTime.getMTime() < publicAPI.getMTime() || model.VBOBuildTime.getMTime() < actor.getMTime() || model.VBOBuildTime.getMTime() < actor.getProperty(model.currentValidInputs[0].inputIndex)?.getMTime() || model.VBOBuildTime.getMTime() < model.renderable.getMTime() || model.currentValidInputs.some(_ref2 => {
1003
+ let {
1004
+ imageData
1005
+ } = _ref2;
1006
+ return model.VBOBuildTime.getMTime() < imageData.getMTime();
1007
+ }) || model.scalarTextures.length !== model.currentValidInputs.length || !model.scalarTextures.every(texture => !!texture?.getHandle()) || !model.colorTexture?.getHandle() || !model.opacityTexture?.getHandle() || !model.labelOutlineThicknessTexture?.getHandle() || !model.jitterTexture?.getHandle();
1026
1008
  publicAPI.buildBufferObjects = (ren, actor) => {
1027
- const image = model.currentInput;
1028
- if (!image) {
1029
- return;
1030
- }
1031
- const scalars = image.getPointData() && image.getPointData().getScalars();
1032
- if (!scalars) {
1033
- return;
1034
- }
1035
- const vprop = actor.getProperty();
1036
1009
  if (!model.jitterTexture.getHandle()) {
1037
- const oTable = new Uint8Array(32 * 32);
1010
+ const jitterArray = new Float32Array(32 * 32);
1038
1011
  for (let i = 0; i < 32 * 32; ++i) {
1039
- oTable[i] = 255.0 * Math.random();
1012
+ jitterArray[i] = Math.random();
1040
1013
  }
1041
- model.jitterTexture.setMinificationFilter(Filter.LINEAR);
1042
- model.jitterTexture.setMagnificationFilter(Filter.LINEAR);
1043
- model.jitterTexture.create2DFromRaw(32, 32, 1, VtkDataTypes.UNSIGNED_CHAR, oTable);
1014
+ model.jitterTexture.setMinificationFilter(Filter.NEAREST);
1015
+ model.jitterTexture.setMagnificationFilter(Filter.NEAREST);
1016
+ model.jitterTexture.create2DFromRaw({
1017
+ width: 32,
1018
+ height: 32,
1019
+ numComps: 1,
1020
+ dataType: VtkDataTypes.FLOAT,
1021
+ data: jitterArray
1022
+ });
1044
1023
  }
1045
- const numComp = scalars.getNumberOfComponents();
1046
- const useIndependentComps = publicAPI.useIndependentComponents(vprop);
1047
- const numIComps = useIndependentComps ? numComp : 1;
1048
- const scalarOpacityFunc = vprop.getScalarOpacity();
1049
- const opTex = model._openGLRenderWindow.getGraphicsResourceForObject(scalarOpacityFunc);
1050
- let toString = getTransferFunctionHash(scalarOpacityFunc, useIndependentComps, numIComps);
1051
- const reBuildOp = !opTex?.oglObject || opTex.hash !== toString;
1024
+ const volumeProperties = actor.getProperties();
1025
+ const firstValidInput = model.currentValidInputs[0];
1026
+ const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex];
1027
+ const numberOfComponents = model.numberOfComponents;
1028
+ const useIndependentComps = model.useIndependentComponents;
1029
+ const numIComps = useIndependentComps ? numberOfComponents : 1;
1030
+
1031
+ // rebuild opacity tfun?
1032
+ const opacityFunctions = [];
1033
+ for (let component = 0; component < numIComps; ++component) {
1034
+ opacityFunctions.push(firstVolumeProperty.getScalarOpacity(component));
1035
+ }
1036
+ const opacityFuncHash = getTransferFunctionsHash(opacityFunctions, useIndependentComps, numIComps);
1037
+ const firstScalarOpacityFunc = firstVolumeProperty.getScalarOpacity();
1038
+ const opTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstScalarOpacityFunc);
1039
+ const reBuildOp = !opTex?.oglObject?.getHandle() || opTex.hash !== opacityFuncHash;
1052
1040
  if (reBuildOp) {
1053
- model.opacityTexture = vtkOpenGLTexture.newInstance();
1054
- model.opacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1055
- // rebuild opacity tfun?
1041
+ const newOpacityTexture = vtkOpenGLTexture.newInstance();
1042
+ newOpacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1056
1043
  let oWidth = model.renderable.getOpacityTextureWidth();
1057
1044
  if (oWidth <= 0) {
1058
1045
  oWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
@@ -1061,8 +1048,8 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1061
1048
  const ofTable = new Float32Array(oSize);
1062
1049
  const tmpTable = new Float32Array(oWidth);
1063
1050
  for (let c = 0; c < numIComps; ++c) {
1064
- const ofun = vprop.getScalarOpacity(c);
1065
- const opacityFactor = publicAPI.getCurrentSampleDistance(ren) / vprop.getScalarOpacityUnitDistance(c);
1051
+ const ofun = firstVolumeProperty.getScalarOpacity(c);
1052
+ const opacityFactor = publicAPI.getCurrentSampleDistance(ren) / firstVolumeProperty.getScalarOpacityUnitDistance(c);
1066
1053
  const oRange = ofun.getRange();
1067
1054
  ofun.getTable(oRange[0], oRange[1], oWidth, tmpTable, 1);
1068
1055
  // adjust for sample distance etc
@@ -1071,43 +1058,57 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1071
1058
  ofTable[c * oWidth * 2 + i + oWidth] = ofTable[c * oWidth * 2 + i];
1072
1059
  }
1073
1060
  }
1074
- model.opacityTexture.resetFormatAndType();
1075
- model.opacityTexture.setMinificationFilter(Filter.LINEAR);
1076
- model.opacityTexture.setMagnificationFilter(Filter.LINEAR);
1061
+ newOpacityTexture.resetFormatAndType();
1062
+ newOpacityTexture.setMinificationFilter(Filter.LINEAR);
1063
+ newOpacityTexture.setMagnificationFilter(Filter.LINEAR);
1077
1064
 
1078
1065
  // use float texture where possible because we really need the resolution
1079
1066
  // for this table. Errors in low values of opacity accumulate to
1080
1067
  // visible artifacts. High values of opacity quickly terminate without
1081
1068
  // artifacts.
1082
1069
  if (model._openGLRenderWindow.getWebgl2() || model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear')) {
1083
- model.opacityTexture.create2DFromRaw(oWidth, 2 * numIComps, 1, VtkDataTypes.FLOAT, ofTable);
1070
+ newOpacityTexture.create2DFromRaw({
1071
+ width: oWidth,
1072
+ height: 2 * numIComps,
1073
+ numComps: 1,
1074
+ dataType: VtkDataTypes.FLOAT,
1075
+ data: ofTable
1076
+ });
1084
1077
  } else {
1085
1078
  const oTable = new Uint8ClampedArray(oSize);
1086
1079
  for (let i = 0; i < oSize; ++i) {
1087
1080
  oTable[i] = 255.0 * ofTable[i];
1088
1081
  }
1089
- model.opacityTexture.create2DFromRaw(oWidth, 2 * numIComps, 1, VtkDataTypes.UNSIGNED_CHAR, oTable);
1082
+ newOpacityTexture.create2DFromRaw({
1083
+ width: oWidth,
1084
+ height: 2 * numIComps,
1085
+ numComps: 1,
1086
+ dataType: VtkDataTypes.UNSIGNED_CHAR,
1087
+ data: oTable
1088
+ });
1090
1089
  }
1091
- if (scalarOpacityFunc) {
1092
- model._openGLRenderWindow.setGraphicsResourceForObject(scalarOpacityFunc, model.opacityTexture, toString);
1093
- if (scalarOpacityFunc !== model._scalarOpacityFunc) {
1094
- model._openGLRenderWindow.registerGraphicsResourceUser(scalarOpacityFunc, publicAPI);
1095
- model._openGLRenderWindow.unregisterGraphicsResourceUser(model._scalarOpacityFunc, publicAPI);
1096
- }
1097
- model._scalarOpacityFunc = scalarOpacityFunc;
1090
+ if (firstScalarOpacityFunc) {
1091
+ model._openGLRenderWindow.setGraphicsResourceForObject(firstScalarOpacityFunc, newOpacityTexture, opacityFuncHash);
1098
1092
  }
1093
+ model.opacityTexture = newOpacityTexture;
1099
1094
  } else {
1100
1095
  model.opacityTexture = opTex.oglObject;
1101
1096
  }
1097
+ replaceGraphicsResource(model._openGLRenderWindow, model._opacityTextureCore, firstScalarOpacityFunc);
1098
+ model._opacityTextureCore = firstScalarOpacityFunc;
1102
1099
 
1103
1100
  // rebuild color tfun?
1104
- const colorTransferFunc = vprop.getRGBTransferFunction();
1105
- toString = getTransferFunctionHash(colorTransferFunc, useIndependentComps, numIComps);
1106
- const cTex = model._openGLRenderWindow.getGraphicsResourceForObject(colorTransferFunc);
1107
- const reBuildC = !cTex?.oglObject?.getHandle() || cTex?.hash !== toString;
1101
+ const colorTransferFunctions = [];
1102
+ for (let component = 0; component < numIComps; ++component) {
1103
+ colorTransferFunctions.push(firstVolumeProperty.getRGBTransferFunction(component));
1104
+ }
1105
+ const colorFuncHash = getTransferFunctionsHash(colorTransferFunctions, useIndependentComps, numIComps);
1106
+ const firstColorTransferFunc = firstVolumeProperty.getRGBTransferFunction();
1107
+ const cTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstColorTransferFunc);
1108
+ const reBuildC = !cTex?.oglObject?.getHandle() || cTex?.hash !== colorFuncHash;
1108
1109
  if (reBuildC) {
1109
- model.colorTexture = vtkOpenGLTexture.newInstance();
1110
- model.colorTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1110
+ const newColorTexture = vtkOpenGLTexture.newInstance();
1111
+ newColorTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1111
1112
  let cWidth = model.renderable.getColorTextureWidth();
1112
1113
  if (cWidth <= 0) {
1113
1114
  cWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
@@ -1116,7 +1117,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1116
1117
  const cTable = new Uint8ClampedArray(cSize);
1117
1118
  const tmpTable = new Float32Array(cWidth * 3);
1118
1119
  for (let c = 0; c < numIComps; ++c) {
1119
- const cfun = vprop.getRGBTransferFunction(c);
1120
+ const cfun = firstVolumeProperty.getRGBTransferFunction(c);
1120
1121
  const cRange = cfun.getRange();
1121
1122
  cfun.getTable(cRange[0], cRange[1], cWidth, tmpTable, 1);
1122
1123
  for (let i = 0; i < cWidth * 3; ++i) {
@@ -1124,57 +1125,122 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1124
1125
  cTable[c * cWidth * 6 + i + cWidth * 3] = 255.0 * tmpTable[i];
1125
1126
  }
1126
1127
  }
1127
- model.colorTexture.resetFormatAndType();
1128
- model.colorTexture.setMinificationFilter(Filter.LINEAR);
1129
- model.colorTexture.setMagnificationFilter(Filter.LINEAR);
1130
- model.colorTexture.create2DFromRaw(cWidth, 2 * numIComps, 3, VtkDataTypes.UNSIGNED_CHAR, cTable);
1131
- if (colorTransferFunc) {
1132
- model._openGLRenderWindow.setGraphicsResourceForObject(colorTransferFunc, model.colorTexture, toString);
1133
- if (colorTransferFunc !== model._colorTransferFunc) {
1134
- model._openGLRenderWindow.registerGraphicsResourceUser(colorTransferFunc, publicAPI);
1135
- model._openGLRenderWindow.unregisterGraphicsResourceUser(model._colorTransferFunc, publicAPI);
1136
- }
1137
- model._colorTransferFunc = colorTransferFunc;
1138
- }
1128
+ newColorTexture.resetFormatAndType();
1129
+ newColorTexture.setMinificationFilter(Filter.LINEAR);
1130
+ newColorTexture.setMagnificationFilter(Filter.LINEAR);
1131
+ newColorTexture.create2DFromRaw({
1132
+ width: cWidth,
1133
+ height: 2 * numIComps,
1134
+ numComps: 3,
1135
+ dataType: VtkDataTypes.UNSIGNED_CHAR,
1136
+ data: cTable
1137
+ });
1138
+ model._openGLRenderWindow.setGraphicsResourceForObject(firstColorTransferFunc, newColorTexture, colorFuncHash);
1139
+ model.colorTexture = newColorTexture;
1139
1140
  } else {
1140
1141
  model.colorTexture = cTex.oglObject;
1141
1142
  }
1142
- publicAPI.updateLabelOutlineThicknessTexture(actor);
1143
- const tex = model._openGLRenderWindow.getGraphicsResourceForObject(scalars);
1144
- // rebuild the scalarTexture if the data has changed
1145
- toString = getImageDataHash(image, scalars);
1146
- const reBuildTex = !tex?.oglObject?.getHandle() || tex?.hash !== toString;
1147
- const updatedExtents = model.renderable.getUpdatedExtents();
1148
- const hasUpdatedExtents = !!updatedExtents.length;
1149
- if (reBuildTex && !hasUpdatedExtents) {
1150
- model.scalarTexture = vtkOpenGLTexture.newInstance();
1151
- model.scalarTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1152
- // Build the textures
1153
- const dims = image.getDimensions();
1154
- // Use norm16 for scalar texture if the extension is available
1155
- model.scalarTexture.setOglNorm16Ext(model.context.getExtension('EXT_texture_norm16'));
1156
- model.scalarTexture.resetFormatAndType();
1157
- model.scalarTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars);
1158
- if (scalars) {
1159
- model._openGLRenderWindow.setGraphicsResourceForObject(scalars, model.scalarTexture, toString);
1160
- if (scalars !== model._scalars) {
1161
- model._openGLRenderWindow.registerGraphicsResourceUser(scalars, publicAPI);
1162
- model._openGLRenderWindow.unregisterGraphicsResourceUser(model._scalars, publicAPI);
1163
- }
1164
- model._scalars = scalars;
1143
+ replaceGraphicsResource(model._openGLRenderWindow, model._colorTextureCore, firstColorTransferFunc);
1144
+ model._colorTextureCore = firstColorTransferFunc;
1145
+
1146
+ // rebuild scalarTextures?
1147
+ model.currentValidInputs.forEach((_ref3, component) => {
1148
+ let {
1149
+ imageData,
1150
+ inputIndex
1151
+ } = _ref3;
1152
+ // rebuild the scalarTexture if the data has changed
1153
+ const volumeProperty = volumeProperties[inputIndex];
1154
+ const scalars = imageData.getPointData().getScalars();
1155
+ const tex = model._openGLRenderWindow.getGraphicsResourceForObject(scalars);
1156
+ const scalarsHash = getImageDataHash(imageData, scalars);
1157
+ const reBuildTex = !tex?.oglObject?.getHandle() || tex?.hash !== scalarsHash;
1158
+ const updatedExtents = volumeProperty.getUpdatedExtents();
1159
+ const hasUpdatedExtents = !!updatedExtents.length;
1160
+ if (reBuildTex && !hasUpdatedExtents) {
1161
+ const newScalarTexture = vtkOpenGLTexture.newInstance();
1162
+ newScalarTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1163
+ // Build the textures
1164
+ const dims = imageData.getDimensions();
1165
+ // Use norm16 for scalar texture if the extension is available
1166
+ newScalarTexture.setOglNorm16Ext(model.context.getExtension('EXT_texture_norm16'));
1167
+ newScalarTexture.resetFormatAndType();
1168
+ newScalarTexture.create3DFilterableFromDataArray({
1169
+ width: dims[0],
1170
+ height: dims[1],
1171
+ depth: dims[2],
1172
+ dataArray: scalars,
1173
+ preferSizeOverAccuracy: volumeProperty.getPreferSizeOverAccuracy()
1174
+ });
1175
+ model._openGLRenderWindow.setGraphicsResourceForObject(scalars, newScalarTexture, scalarsHash);
1176
+ model.scalarTextures[component] = newScalarTexture;
1177
+ } else {
1178
+ model.scalarTextures[component] = tex.oglObject;
1179
+ }
1180
+ if (hasUpdatedExtents) {
1181
+ // If hasUpdatedExtents, then the texture is partially updated.
1182
+ // clear the array to acknowledge the update.
1183
+ volumeProperty.setUpdatedExtents([]);
1184
+ const dims = imageData.getDimensions();
1185
+ model.scalarTextures[component].create3DFilterableFromDataArray({
1186
+ width: dims[0],
1187
+ height: dims[1],
1188
+ depth: dims[2],
1189
+ dataArray: scalars,
1190
+ updatedExtents
1191
+ });
1192
+ }
1193
+ replaceGraphicsResource(model._openGLRenderWindow, model._scalarTexturesCore[component], scalars);
1194
+ model._scalarTexturesCore[component] = scalars;
1195
+ });
1196
+
1197
+ // rebuild label outline thickness texture?
1198
+ const labelOutlineThicknessArray = firstVolumeProperty.getLabelOutlineThickness();
1199
+ const lTex = model._openGLRenderWindow.getGraphicsResourceForObject(labelOutlineThicknessArray);
1200
+ const labelOutlineThicknessHash = labelOutlineThicknessArray.join('-');
1201
+ const reBuildL = !lTex?.oglObject?.getHandle() || lTex?.hash !== labelOutlineThicknessHash;
1202
+ if (reBuildL) {
1203
+ const newLabelOutlineThicknessTexture = vtkOpenGLTexture.newInstance();
1204
+ newLabelOutlineThicknessTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1205
+ let lWidth = model.renderable.getLabelOutlineTextureWidth();
1206
+ if (lWidth <= 0) {
1207
+ lWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
1208
+ }
1209
+ const lHeight = 1;
1210
+ const lSize = lWidth * lHeight;
1211
+ const lTable = new Uint8Array(lSize);
1212
+
1213
+ // Assuming labelOutlineThicknessArray contains the thickness for each segment
1214
+ for (let i = 0; i < lWidth; ++i) {
1215
+ // Retrieve the thickness value for the current segment index.
1216
+ // If the value is undefined, use the first element's value as a default, otherwise use the value (even if 0)
1217
+ const thickness = typeof labelOutlineThicknessArray[i] !== 'undefined' ? labelOutlineThicknessArray[i] : labelOutlineThicknessArray[0];
1218
+ lTable[i] = thickness;
1165
1219
  }
1220
+ newLabelOutlineThicknessTexture.resetFormatAndType();
1221
+ newLabelOutlineThicknessTexture.setMinificationFilter(Filter.NEAREST);
1222
+ newLabelOutlineThicknessTexture.setMagnificationFilter(Filter.NEAREST);
1223
+
1224
+ // Create a 2D texture (acting as 1D) from the raw data
1225
+ newLabelOutlineThicknessTexture.create2DFromRaw({
1226
+ width: lWidth,
1227
+ height: lHeight,
1228
+ numComps: 1,
1229
+ dataType: VtkDataTypes.UNSIGNED_CHAR,
1230
+ data: lTable
1231
+ });
1232
+ if (labelOutlineThicknessArray) {
1233
+ model._openGLRenderWindow.setGraphicsResourceForObject(labelOutlineThicknessArray, newLabelOutlineThicknessTexture, labelOutlineThicknessHash);
1234
+ }
1235
+ model.labelOutlineThicknessTexture = newLabelOutlineThicknessTexture;
1166
1236
  } else {
1167
- model.scalarTexture = tex.oglObject;
1168
- }
1169
- if (hasUpdatedExtents) {
1170
- // If hasUpdatedExtents, then the texture is partially updated.
1171
- // clear the array to acknowledge the update.
1172
- model.renderable.setUpdatedExtents([]);
1173
- const dims = image.getDimensions();
1174
- model.scalarTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars, false, updatedExtents);
1237
+ model.labelOutlineThicknessTexture = lTex.oglObject;
1175
1238
  }
1239
+ replaceGraphicsResource(model._openGLRenderWindow, model._labelOutlineThicknessTextureCore, labelOutlineThicknessArray);
1240
+ model._labelOutlineThicknessTextureCore = labelOutlineThicknessArray;
1241
+
1242
+ // rebuild the CABO?
1176
1243
  if (!model.tris.getCABO().getElementCount()) {
1177
- // build the CABO
1178
1244
  const ptsArray = new Float32Array(12);
1179
1245
  for (let i = 0; i < 4; i++) {
1180
1246
  ptsArray[i * 3] = i % 2 * 2 - 1.0;
@@ -1190,33 +1256,6 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1190
1256
  cellArray[5] = 0;
1191
1257
  cellArray[6] = 3;
1192
1258
  cellArray[7] = 2;
1193
-
1194
- // const dim = 12.0;
1195
- // const ptsArray = new Float32Array(3 * dim * dim);
1196
- // for (let i = 0; i < dim; i++) {
1197
- // for (let j = 0; j < dim; j++) {
1198
- // const offset = ((i * dim) + j) * 3;
1199
- // ptsArray[offset] = (2.0 * (i / (dim - 1.0))) - 1.0;
1200
- // ptsArray[offset + 1] = (2.0 * (j / (dim - 1.0))) - 1.0;
1201
- // ptsArray[offset + 2] = -1.0;
1202
- // }
1203
- // }
1204
-
1205
- // const cellArray = new Uint16Array(8 * (dim - 1) * (dim - 1));
1206
- // for (let i = 0; i < dim - 1; i++) {
1207
- // for (let j = 0; j < dim - 1; j++) {
1208
- // const offset = 8 * ((i * (dim - 1)) + j);
1209
- // cellArray[offset] = 3;
1210
- // cellArray[offset + 1] = (i * dim) + j;
1211
- // cellArray[offset + 2] = (i * dim) + 1 + j;
1212
- // cellArray[offset + 3] = ((i + 1) * dim) + 1 + j;
1213
- // cellArray[offset + 4] = 3;
1214
- // cellArray[offset + 5] = (i * dim) + j;
1215
- // cellArray[offset + 6] = ((i + 1) * dim) + 1 + j;
1216
- // cellArray[offset + 7] = ((i + 1) * dim) + j;
1217
- // }
1218
- // }
1219
-
1220
1259
  const points = vtkDataArray.newInstance({
1221
1260
  numberOfComponents: 3,
1222
1261
  values: ptsArray
@@ -1233,56 +1272,6 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1233
1272
  }
1234
1273
  model.VBOBuildTime.modified();
1235
1274
  };
1236
- publicAPI.updateLabelOutlineThicknessTexture = volume => {
1237
- const labelOutlineThicknessArray = volume.getProperty().getLabelOutlineThickness();
1238
- const lTex = model._openGLRenderWindow.getGraphicsResourceForObject(labelOutlineThicknessArray);
1239
-
1240
- // compute the join of the labelOutlineThicknessArray so that
1241
- // we can use it to decide whether to rebuild the labelOutlineThicknessTexture
1242
- // or not
1243
- const toString = `${labelOutlineThicknessArray.join('-')}`;
1244
- const reBuildL = !lTex?.oglObject?.getHandle() || lTex?.hash !== toString;
1245
- if (reBuildL) {
1246
- model.labelOutlineThicknessTexture = vtkOpenGLTexture.newInstance();
1247
- model.labelOutlineThicknessTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1248
- let lWidth = model.renderable.getLabelOutlineTextureWidth();
1249
- if (lWidth <= 0) {
1250
- lWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
1251
- }
1252
- const lHeight = 1;
1253
- const lSize = lWidth * lHeight;
1254
- const lTable = new Uint8Array(lSize);
1255
-
1256
- // Assuming labelOutlineThicknessArray contains the thickness for each segment
1257
- for (let i = 0; i < lWidth; ++i) {
1258
- // Retrieve the thickness value for the current segment index.
1259
- // If the value is undefined, use the first element's value as a default, otherwise use the value (even if 0)
1260
- const thickness = typeof labelOutlineThicknessArray[i] !== 'undefined' ? labelOutlineThicknessArray[i] : labelOutlineThicknessArray[0];
1261
- lTable[i] = thickness;
1262
- }
1263
- model.labelOutlineThicknessTexture.resetFormatAndType();
1264
- model.labelOutlineThicknessTexture.setMinificationFilter(Filter.NEAREST);
1265
- model.labelOutlineThicknessTexture.setMagnificationFilter(Filter.NEAREST);
1266
-
1267
- // Create a 2D texture (acting as 1D) from the raw data
1268
- model.labelOutlineThicknessTexture.create2DFromRaw(lWidth, lHeight, 1, VtkDataTypes.UNSIGNED_CHAR, lTable);
1269
- if (labelOutlineThicknessArray) {
1270
- model._openGLRenderWindow.setGraphicsResourceForObject(labelOutlineThicknessArray, model.labelOutlineThicknessTexture, toString);
1271
- if (labelOutlineThicknessArray !== model._labelOutlineThicknessArray) {
1272
- model._openGLRenderWindow.registerGraphicsResourceUser(labelOutlineThicknessArray, publicAPI);
1273
- model._openGLRenderWindow.unregisterGraphicsResourceUser(model._labelOutlineThicknessArray, publicAPI);
1274
- }
1275
- model._labelOutlineThicknessArray = labelOutlineThicknessArray;
1276
- }
1277
- } else {
1278
- model.labelOutlineThicknessTexture = lTex.oglObject;
1279
- }
1280
- };
1281
- publicAPI.isLabelmapOutlineRequired = actor => {
1282
- const prop = actor.getProperty();
1283
- const renderable = model.renderable;
1284
- return prop.getUseLabelOutline() || renderable.getBlendMode() === BlendMode.LABELMAP_EDGE_PROJECTION_BLEND;
1285
- };
1286
1275
  }
1287
1276
 
1288
1277
  // ----------------------------------------------------------------------------
@@ -1292,14 +1281,15 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1292
1281
  const DEFAULT_VALUES = {
1293
1282
  context: null,
1294
1283
  VBOBuildTime: null,
1295
- scalarTexture: null,
1284
+ scalarTextures: [],
1285
+ _scalarTexturesCore: [],
1296
1286
  opacityTexture: null,
1297
- opacityTextureString: null,
1287
+ _opacityTextureCore: null,
1298
1288
  colorTexture: null,
1299
- colorTextureString: null,
1300
- jitterTexture: null,
1289
+ _colorTextureCore: null,
1301
1290
  labelOutlineThicknessTexture: null,
1302
- labelOutlineThicknessTextureString: null,
1291
+ _labelOutlineThicknessTextureCore: null,
1292
+ jitterTexture: null,
1303
1293
  tris: null,
1304
1294
  framebuffer: null,
1305
1295
  copyShader: null,
@@ -1308,18 +1298,13 @@ const DEFAULT_VALUES = {
1308
1298
  targetXYF: 1.0,
1309
1299
  zBufferTexture: null,
1310
1300
  lastZBufferTexture: null,
1311
- lightComplexity: 0,
1312
1301
  fullViewportTime: 1.0,
1313
1302
  idxToView: null,
1314
- idxNormalMatrix: null,
1303
+ vecISToVCMatrix: null,
1315
1304
  modelToView: null,
1316
1305
  projectionToView: null,
1317
1306
  avgWindowArea: 0.0,
1318
1307
  avgFrameTime: 0.0
1319
- // _scalars: null,
1320
- // _scalarOpacityFunc: null,
1321
- // _colorTransferFunc: null,
1322
- // _labelOutlineThicknessArray: null,
1323
1308
  };
1324
1309
 
1325
1310
  // ----------------------------------------------------------------------------
@@ -1340,11 +1325,6 @@ function extend(publicAPI, model) {
1340
1325
  model.jitterTexture.setWrapS(Wrap.REPEAT);
1341
1326
  model.jitterTexture.setWrapT(Wrap.REPEAT);
1342
1327
  model.framebuffer = vtkOpenGLFramebuffer.newInstance();
1343
- model.idxToView = mat4.identity(new Float64Array(16));
1344
- model.idxNormalMatrix = mat3.identity(new Float64Array(9));
1345
- model.modelToView = mat4.identity(new Float64Array(16));
1346
- model.projectionToView = mat4.identity(new Float64Array(16));
1347
- model.projectionToWorld = mat4.identity(new Float64Array(16));
1348
1328
 
1349
1329
  // Build VTK API
1350
1330
  setGet(publicAPI, model, ['context']);