@kitware/vtk.js 33.0.0-beta.4 → 33.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/BREAKING_CHANGES.md +0 -3
  2. package/Common/Core/ScalarsToColors/Constants.js +7 -2
  3. package/Common/Core/ScalarsToColors.js +3 -1
  4. package/Rendering/Core/Actor.d.ts +20 -5
  5. package/Rendering/Core/Actor.js +68 -5
  6. package/Rendering/Core/ColorTransferFunction.js +26 -35
  7. package/Rendering/Core/ImageCPRMapper.d.ts +20 -1
  8. package/Rendering/Core/ImageCPRMapper.js +2 -1
  9. package/Rendering/Core/ImageProperty.d.ts +1 -20
  10. package/Rendering/Core/ImageProperty.js +5 -7
  11. package/Rendering/Core/ImageResliceMapper.d.ts +20 -1
  12. package/Rendering/Core/ImageResliceMapper.js +2 -1
  13. package/Rendering/Core/ImageSlice.d.ts +23 -7
  14. package/Rendering/Core/ImageSlice.js +68 -9
  15. package/Rendering/Core/Mapper.js +8 -16
  16. package/Rendering/Core/Prop3D.d.ts +2 -39
  17. package/Rendering/Core/Prop3D.js +2 -81
  18. package/Rendering/Core/ScalarBarActor.js +4 -2
  19. package/Rendering/Core/Volume.d.ts +20 -5
  20. package/Rendering/Core/Volume.js +70 -2
  21. package/Rendering/Core/VolumeMapper/Constants.d.ts +7 -0
  22. package/Rendering/Core/VolumeMapper/Constants.js +8 -2
  23. package/Rendering/Core/VolumeMapper.d.ts +173 -16
  24. package/Rendering/Core/VolumeMapper.js +51 -16
  25. package/Rendering/Core/VolumeProperty/Constants.d.ts +3 -12
  26. package/Rendering/Core/VolumeProperty/Constants.js +4 -11
  27. package/Rendering/Core/VolumeProperty.d.ts +5 -140
  28. package/Rendering/Core/VolumeProperty.js +7 -54
  29. package/Rendering/OpenGL/ImageCPRMapper.js +21 -30
  30. package/Rendering/OpenGL/ImageMapper.js +27 -27
  31. package/Rendering/OpenGL/ImageResliceMapper.js +183 -271
  32. package/Rendering/OpenGL/PolyDataMapper.js +8 -1
  33. package/Rendering/OpenGL/RenderWindow/resourceSharingHelper.d.ts +3 -3
  34. package/Rendering/OpenGL/RenderWindow/resourceSharingHelper.js +5 -8
  35. package/Rendering/OpenGL/VolumeMapper.js +784 -722
  36. package/Rendering/OpenGL/glsl/vtkVolumeFS.glsl.js +1 -1
  37. package/Rendering/WebGPU/VolumePassFSQ.js +2 -2
  38. package/index.d.ts +0 -1
  39. package/macros2.js +1 -1
  40. package/package.json +1 -1
  41. package/Interaction/Manipulators/KeyboardCameraManipulator.d.ts +0 -113
@@ -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';
5
4
  import vtkDataArray from '../../Common/Core/DataArray.js';
6
5
  import { VtkDataTypes } from '../../Common/Core/DataArray/Constants.js';
7
6
  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 { ColorMixPreset, OpacityMode, InterpolationType } from '../Core/VolumeProperty/Constants.js';
16
+ import { InterpolationType, OpacityMode, ColorMixPreset } from '../Core/VolumeProperty/Constants.js';
17
17
  import { BlendMode } from '../Core/VolumeMapper/Constants.js';
18
- import { getTransferFunctionsHash, getImageDataHash } from './RenderWindow/resourceSharingHelper.js';
18
+ import { getTransferFunctionHash, 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,14 +29,81 @@ const {
29
29
  // helper methods
30
30
  // ----------------------------------------------------------------------------
31
31
 
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
- };
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
+ }
40
107
 
41
108
  // ----------------------------------------------------------------------------
42
109
  // vtkOpenGLVolumeMapper methods
@@ -45,51 +112,8 @@ const preAllocatedMatrices = {
45
112
  function vtkOpenGLVolumeMapper(publicAPI, model) {
46
113
  // Set our className
47
114
  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
- }
90
115
  function unregisterGraphicsResources(renderWindow) {
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));
116
+ [model._scalars, model._scalarOpacityFunc, model._colorTransferFunc, model._labelOutlineThicknessArray].forEach(coreObject => renderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI));
93
117
  }
94
118
  publicAPI.buildPass = () => {
95
119
  model.zBufferTexture = null;
@@ -133,175 +157,216 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
133
157
  shaders.Fragment = vtkVolumeFS;
134
158
  shaders.Geometry = '';
135
159
  };
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
+ };
136
167
  publicAPI.replaceShaderValues = (shaders, ren, actor) => {
168
+ const actorProps = actor.getProperty();
137
169
  let FSSource = shaders.Fragment;
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');
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;
142
175
  }
143
- if (model.previousState.volumeLightingEnabled) {
144
- enabledLightings.push('Volume');
176
+ const vtkImageLabelOutline = publicAPI.isLabelmapOutlineRequired(actor);
177
+ if (vtkImageLabelOutline === true) {
178
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ImageLabelOutlineOn', '#define vtkImageLabelOutlineOn').result;
145
179
  }
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;
180
+ const LabelEdgeProjection = model.renderable.getBlendMode() === BlendMode.LABELMAP_EDGE_PROJECTION_BLEND;
181
+ if (LabelEdgeProjection) {
182
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::LabelEdgeProjectionOn', '#define vtkLabelEdgeProjectionOn').result;
149
183
  }
150
- if (model.previousState.useIndependentComponents) {
151
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::EnabledIndependentComponents', '#define EnabledIndependentComponents').result;
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;
152
189
  }
153
- if (model.previousState.gradientOpacityEnabled) {
154
- FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::EnabledGradientOpacity', '#define EnabledGradientOpacity').result;
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;
155
253
  }
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;
158
254
 
159
255
  // if we have a ztexture then declare it and use it
160
- if (model.previousState.hasZBufferTexture) {
256
+ if (model.zBufferTexture !== null) {
161
257
  FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ZBuffer::Dec', ['uniform sampler2D zBufferTexture;', 'uniform float vpZWidth;', 'uniform float vpZHeight;']).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;
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;
163
259
  }
164
260
 
165
261
  // Set the BlendMode approach
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;
171
- shaders.Fragment = FSSource;
172
- const numberOfClippingPlanes = model.previousState.numberOfClippingPlanes;
173
- if (numberOfClippingPlanes > 0) {
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;
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;
176
- }
262
+ FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::BlendMode', `${model.renderable.getBlendMode()}`).result;
177
263
  shaders.Fragment = FSSource;
264
+ publicAPI.replaceShaderLight(shaders, ren, actor);
265
+ publicAPI.replaceShaderClippingPlane(shaders, ren, actor);
178
266
  };
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
- }
267
+ publicAPI.replaceShaderLight = (shaders, ren, actor) => {
268
+ if (model.lightComplexity === 0) {
269
+ return;
215
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
216
276
 
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;
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;
237
282
  }
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;
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
+ }
295
+ 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;
301
+ 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;
303
+ }
304
+ shaders.Fragment = FSSource;
305
+ };
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
+ }
249
321
  }
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
- }
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
+ });
265
329
  }
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
- }
330
+ if (lightComplexity !== model.lightComplexity) {
331
+ model.lightComplexity = lightComplexity;
332
+ publicAPI.modified();
333
+ }
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));
277
344
  }
278
- const currentState = {
279
- numberOfComponents,
280
- useIndependentComponents,
281
- proportionalComponents,
282
- forceNearestComponents,
283
- blendMode,
284
- numberOfLights,
285
- numberOfValidInputs,
286
- maximumNumberOfSamples,
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(),
287
360
  hasZBufferTexture,
288
- maxLaoKernelSize,
289
- numberOfClippingPlanes,
290
- mapperShaderReplacements,
291
- renderPassShaderReplacements,
292
- colorForValueFunctionId,
293
- surfaceLightingEnabled,
294
- volumeLightingEnabled,
295
- forceNearestInterpolationEnabled,
296
- multiTexturePerVolumeEnabled,
297
- gradientOpacityEnabled
361
+ opacityModes,
362
+ forceNearestInterps
298
363
  };
299
364
 
300
365
  // We need to rebuild the shader if one of these variables has changed,
301
366
  // since they are used in the shader template replacement step.
302
367
  // We also need to rebuild if the shader source time is outdated.
303
- if (cellBO.getProgram()?.getHandle() === 0 || !model.previousState || !DeepEqual(model.previousState, currentState)) {
304
- model.previousState = currentState;
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;
305
370
  return true;
306
371
  }
307
372
  return false;
@@ -346,49 +411,27 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
346
411
  }
347
412
  cellBO.getAttributeUpdateTime().modified();
348
413
  }
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) {
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++) {
368
425
  // convert iprange from 0-1 into data range values
369
- minVals[component] = ipScalarRange[0] * volInfo.dataComputedScale[volInfoIndex] + volInfo.dataComputedOffset[volInfoIndex];
370
- maxVals[component] = ipScalarRange[1] * volInfo.dataComputedScale[volInfoIndex] + volInfo.dataComputedOffset[volInfoIndex];
426
+ minVals[i] = ipScalarRange[0] * volInfo.dataComputedScale[i] + volInfo.dataComputedOffset[i];
427
+ maxVals[i] = ipScalarRange[1] * volInfo.dataComputedScale[i] + volInfo.dataComputedOffset[i];
371
428
  // convert data ranges into texture values
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);
429
+ minVals[i] = (minVals[i] - volInfo.offset[i]) / volInfo.scale[i];
430
+ maxVals[i] = (maxVals[i] - volInfo.offset[i]) / volInfo.scale[i];
387
431
  }
432
+ program.setUniform4f('ipScalarRangeMin', minVals[0], minVals[1], minVals[2], minVals[3]);
433
+ program.setUniform4f('ipScalarRangeMax', maxVals[0], maxVals[1], maxVals[2], maxVals[3]);
388
434
  }
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]);
392
435
 
393
436
  // if we have a zbuffer texture then set it
394
437
  if (model.zBufferTexture !== null) {
@@ -399,174 +442,140 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
399
442
  }
400
443
  };
401
444
  publicAPI.setCameraShaderParameters = (cellBO, ren, actor) => {
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
445
+ // // [WMVP]C == {world, model, view, projection} coordinates
446
+ // // E.g., WCPC == world to projection coordinate transformation
413
447
  const keyMats = model.openGLCamera.getKeyMatrices(ren);
414
448
  const actMats = model.openGLVolume.getKeyMatrices();
415
- mat4.multiply(modelToView, keyMats.wcvc, actMats.mcwc);
449
+ mat4.multiply(model.modelToView, keyMats.wcvc, actMats.mcwc);
416
450
  const program = cellBO.getProgram();
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);
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);
440
480
  }
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();
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);
464
487
  }
465
- program.setUniform2fv('kernelSample', kernelSample);
466
-
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
- });
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);
513
530
  }
514
531
 
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)) {
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);
572
+ }
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();
570
579
  const distance = camera.getDistance();
571
580
 
572
581
  // set the clipping range to be model.distance and model.distance + 0.1
@@ -579,151 +588,186 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
579
588
  const labelOutlineKeyMats = model.openGLCamera.getKeyMatrices(ren);
580
589
 
581
590
  // Get the projection coordinate to world coordinate transformation matrix.
582
- mat4.invert(projectionToWorld, labelOutlineKeyMats.wcpc);
591
+ mat4.invert(model.projectionToWorld, labelOutlineKeyMats.wcpc);
583
592
 
584
593
  // reset the clipping range since the keyMats are cached
585
- camera.setClippingRange(clippingRange[0], clippingRange[1]);
594
+ camera.setClippingRange(cRange0, cRange1);
586
595
 
587
596
  // to re compute the matrices for the current camera and cache them
588
597
  model.openGLCamera.getKeyMatrices(ren);
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);
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());
604
692
  }
605
693
  };
606
694
  publicAPI.setPropertyShaderParameters = (cellBO, ren, actor) => {
607
695
  const program = cellBO.getProgram();
696
+ program.setUniformi('ctexture', model.colorTexture.getTextureUnit());
697
+ program.setUniformi('otexture', model.opacityTexture.getTextureUnit());
608
698
  program.setUniformi('jtexture', model.jitterTexture.getTextureUnit());
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;
699
+ program.setUniformi('ttexture', model.labelOutlineThicknessTexture.getTextureUnit());
700
+ const volInfo = model.scalarTexture.getVolumeInfo();
701
+ const vprop = actor.getProperty();
620
702
 
621
703
  // set the component mix when independent
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;
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));
632
709
  }
633
- program.setUniform4fv(`${uniformPrefix}.transferFunctionsSampleHeight`, transferFunctionsSampleHeight);
634
710
  }
635
- const colorForValueFunctionId = model.colorForValueFunctionId;
636
- program.setUniformi(`${uniformPrefix}.colorForValueFunctionId`, colorForValueFunctionId);
637
- const computeNormalFromOpacity = firstVolumeProperty.getComputeNormalFromOpacity();
638
- program.setUniformi(`${uniformPrefix}.computeNormalFromOpacity`, computeNormalFromOpacity);
639
711
 
640
712
  // three levels of shift scale combined into one
641
713
  // for performance in the fragment shader
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);
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);
685
735
  if (useGO) {
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];
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);
692
743
  } else {
693
- gradientOpacityMin[component] = 1;
694
- gradientOpacityMax[component] = 1;
695
- gradientOpacityScale[component] = 0;
696
- gradientOpacityShift[component] = 1;
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);
697
748
  }
698
749
  }
699
750
  } else {
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];
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);
713
759
  }
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);
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());
727
771
  }
728
772
  };
729
773
  publicAPI.getClippingPlaneShaderParameters = (cellBO, ren, actor) => {
@@ -867,22 +911,14 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
867
911
  publicAPI.updateBufferObjects(ren, actor);
868
912
 
869
913
  // set interpolation on the texture based on property setting
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
- });
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
+ }
886
922
 
887
923
  // if we have a zbuffer texture then activate it
888
924
  if (model.zBufferTexture !== null) {
@@ -893,14 +929,24 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
893
929
  const gl = model.context;
894
930
 
895
931
  // render the texture
896
- const allTextures = [...model.scalarTextures, model.colorTexture, model.opacityTexture, model.labelOutlineThicknessTexture, model.jitterTexture];
897
- allTextures.forEach(texture => texture.activate());
932
+ model.scalarTexture.activate();
933
+ model.opacityTexture.activate();
934
+ model.labelOutlineThicknessTexture.activate();
935
+ model.colorTexture.activate();
936
+ model.jitterTexture.activate();
898
937
  publicAPI.updateShaders(model.tris, ren, actor);
899
938
 
900
939
  // 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
+ // }
901
943
  gl.drawArrays(gl.TRIANGLES, 0, model.tris.getCABO().getElementCount());
902
944
  model.tris.getVAO().release();
903
- allTextures.forEach(texture => texture.deactivate());
945
+ model.scalarTexture.deactivate();
946
+ model.colorTexture.deactivate();
947
+ model.opacityTexture.deactivate();
948
+ model.labelOutlineThicknessTexture.deactivate();
949
+ model.jitterTexture.deactivate();
904
950
  };
905
951
  publicAPI.renderPieceFinish = (ren, actor) => {
906
952
  // if we have a zbuffer texture then deactivate it
@@ -944,96 +990,69 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
944
990
  publicAPI.invokeEvent({
945
991
  type: 'StartEvent'
946
992
  });
947
-
948
- // Get the valid image data inputs
949
993
  model.renderable.update();
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
- }
994
+ model.currentInput = model.renderable.getInputData();
980
995
  publicAPI.invokeEvent({
981
996
  type: 'EndEvent'
982
997
  });
983
- if (model.currentValidInputs.length === 0) {
998
+ if (!model.currentInput) {
999
+ vtkErrorMacro('No input!');
984
1000
  return;
985
1001
  }
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);
992
1002
  publicAPI.renderPieceStart(ren, actor);
993
1003
  publicAPI.renderPieceDraw(ren, actor);
994
1004
  publicAPI.renderPieceFinish(ren, actor);
995
1005
  };
1006
+ publicAPI.computeBounds = (ren, actor) => {
1007
+ if (!publicAPI.getInput()) {
1008
+ uninitializeBounds(model.Bounds);
1009
+ return;
1010
+ }
1011
+ model.bounds = publicAPI.getInput().getBounds();
1012
+ };
996
1013
  publicAPI.updateBufferObjects = (ren, actor) => {
997
1014
  // Rebuild buffers if needed
998
1015
  if (publicAPI.getNeedToRebuildBufferObjects(ren, actor)) {
999
1016
  publicAPI.buildBufferObjects(ren, actor);
1000
1017
  }
1001
1018
  };
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();
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
+ };
1008
1026
  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();
1009
1036
  if (!model.jitterTexture.getHandle()) {
1010
- const jitterArray = new Float32Array(32 * 32);
1037
+ const oTable = new Uint8Array(32 * 32);
1011
1038
  for (let i = 0; i < 32 * 32; ++i) {
1012
- jitterArray[i] = Math.random();
1039
+ oTable[i] = 255.0 * Math.random();
1013
1040
  }
1014
- model.jitterTexture.setMinificationFilter(Filter.NEAREST);
1015
- model.jitterTexture.setMagnificationFilter(Filter.NEAREST);
1016
- model.jitterTexture.create2DFromRaw(32, 32, 1, VtkDataTypes.FLOAT, jitterArray);
1017
- }
1018
- const volumeProperties = actor.getProperties();
1019
- const firstValidInput = model.currentValidInputs[0];
1020
- const firstVolumeProperty = volumeProperties[firstValidInput.inputIndex];
1021
- const numberOfComponents = model.numberOfComponents;
1022
- const useIndependentComps = model.useIndependentComponents;
1023
- const numIComps = useIndependentComps ? numberOfComponents : 1;
1024
-
1025
- // rebuild opacity tfun?
1026
- const opacityFunctions = [];
1027
- for (let component = 0; component < numIComps; ++component) {
1028
- opacityFunctions.push(firstVolumeProperty.getScalarOpacity(component));
1029
- }
1030
- const opacityFuncHash = getTransferFunctionsHash(opacityFunctions, useIndependentComps, numIComps);
1031
- const firstScalarOpacityFunc = firstVolumeProperty.getScalarOpacity();
1032
- const opTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstScalarOpacityFunc);
1033
- const reBuildOp = !opTex?.oglObject?.getHandle() || opTex.hash !== opacityFuncHash;
1041
+ model.jitterTexture.setMinificationFilter(Filter.LINEAR);
1042
+ model.jitterTexture.setMagnificationFilter(Filter.LINEAR);
1043
+ model.jitterTexture.create2DFromRaw(32, 32, 1, VtkDataTypes.UNSIGNED_CHAR, oTable);
1044
+ }
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;
1034
1052
  if (reBuildOp) {
1035
- const newOpacityTexture = vtkOpenGLTexture.newInstance();
1036
- newOpacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1053
+ model.opacityTexture = vtkOpenGLTexture.newInstance();
1054
+ model.opacityTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1055
+ // rebuild opacity tfun?
1037
1056
  let oWidth = model.renderable.getOpacityTextureWidth();
1038
1057
  if (oWidth <= 0) {
1039
1058
  oWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
@@ -1042,8 +1061,8 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1042
1061
  const ofTable = new Float32Array(oSize);
1043
1062
  const tmpTable = new Float32Array(oWidth);
1044
1063
  for (let c = 0; c < numIComps; ++c) {
1045
- const ofun = firstVolumeProperty.getScalarOpacity(c);
1046
- const opacityFactor = publicAPI.getCurrentSampleDistance(ren) / firstVolumeProperty.getScalarOpacityUnitDistance(c);
1064
+ const ofun = vprop.getScalarOpacity(c);
1065
+ const opacityFactor = publicAPI.getCurrentSampleDistance(ren) / vprop.getScalarOpacityUnitDistance(c);
1047
1066
  const oRange = ofun.getRange();
1048
1067
  ofun.getTable(oRange[0], oRange[1], oWidth, tmpTable, 1);
1049
1068
  // adjust for sample distance etc
@@ -1052,45 +1071,43 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1052
1071
  ofTable[c * oWidth * 2 + i + oWidth] = ofTable[c * oWidth * 2 + i];
1053
1072
  }
1054
1073
  }
1055
- newOpacityTexture.resetFormatAndType();
1056
- newOpacityTexture.setMinificationFilter(Filter.LINEAR);
1057
- newOpacityTexture.setMagnificationFilter(Filter.LINEAR);
1074
+ model.opacityTexture.resetFormatAndType();
1075
+ model.opacityTexture.setMinificationFilter(Filter.LINEAR);
1076
+ model.opacityTexture.setMagnificationFilter(Filter.LINEAR);
1058
1077
 
1059
1078
  // use float texture where possible because we really need the resolution
1060
1079
  // for this table. Errors in low values of opacity accumulate to
1061
1080
  // visible artifacts. High values of opacity quickly terminate without
1062
1081
  // artifacts.
1063
1082
  if (model._openGLRenderWindow.getWebgl2() || model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear')) {
1064
- newOpacityTexture.create2DFromRaw(oWidth, 2 * numIComps, 1, VtkDataTypes.FLOAT, ofTable);
1083
+ model.opacityTexture.create2DFromRaw(oWidth, 2 * numIComps, 1, VtkDataTypes.FLOAT, ofTable);
1065
1084
  } else {
1066
1085
  const oTable = new Uint8ClampedArray(oSize);
1067
1086
  for (let i = 0; i < oSize; ++i) {
1068
1087
  oTable[i] = 255.0 * ofTable[i];
1069
1088
  }
1070
- newOpacityTexture.create2DFromRaw(oWidth, 2 * numIComps, 1, VtkDataTypes.UNSIGNED_CHAR, oTable);
1089
+ model.opacityTexture.create2DFromRaw(oWidth, 2 * numIComps, 1, VtkDataTypes.UNSIGNED_CHAR, oTable);
1071
1090
  }
1072
- if (firstScalarOpacityFunc) {
1073
- model._openGLRenderWindow.setGraphicsResourceForObject(firstScalarOpacityFunc, newOpacityTexture, opacityFuncHash);
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;
1074
1098
  }
1075
- model.opacityTexture = newOpacityTexture;
1076
1099
  } else {
1077
1100
  model.opacityTexture = opTex.oglObject;
1078
1101
  }
1079
- replaceGraphicsResource(model._openGLRenderWindow, model._opacityTextureCore, firstScalarOpacityFunc);
1080
- model._opacityTextureCore = firstScalarOpacityFunc;
1081
1102
 
1082
1103
  // rebuild color tfun?
1083
- const colorTransferFunctions = [];
1084
- for (let component = 0; component < numIComps; ++component) {
1085
- colorTransferFunctions.push(firstVolumeProperty.getRGBTransferFunction(component));
1086
- }
1087
- const colorFuncHash = getTransferFunctionsHash(colorTransferFunctions, useIndependentComps, numIComps);
1088
- const firstColorTransferFunc = firstVolumeProperty.getRGBTransferFunction();
1089
- const cTex = model._openGLRenderWindow.getGraphicsResourceForObject(firstColorTransferFunc);
1090
- const reBuildC = !cTex?.oglObject?.getHandle() || cTex?.hash !== colorFuncHash;
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;
1091
1108
  if (reBuildC) {
1092
- const newColorTexture = vtkOpenGLTexture.newInstance();
1093
- newColorTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1109
+ model.colorTexture = vtkOpenGLTexture.newInstance();
1110
+ model.colorTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1094
1111
  let cWidth = model.renderable.getColorTextureWidth();
1095
1112
  if (cWidth <= 0) {
1096
1113
  cWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
@@ -1099,7 +1116,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1099
1116
  const cTable = new Uint8ClampedArray(cSize);
1100
1117
  const tmpTable = new Float32Array(cWidth * 3);
1101
1118
  for (let c = 0; c < numIComps; ++c) {
1102
- const cfun = firstVolumeProperty.getRGBTransferFunction(c);
1119
+ const cfun = vprop.getRGBTransferFunction(c);
1103
1120
  const cRange = cfun.getRange();
1104
1121
  cfun.getTable(cRange[0], cRange[1], cWidth, tmpTable, 1);
1105
1122
  for (let i = 0; i < cWidth * 3; ++i) {
@@ -1107,98 +1124,57 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1107
1124
  cTable[c * cWidth * 6 + i + cWidth * 3] = 255.0 * tmpTable[i];
1108
1125
  }
1109
1126
  }
1110
- newColorTexture.resetFormatAndType();
1111
- newColorTexture.setMinificationFilter(Filter.LINEAR);
1112
- newColorTexture.setMagnificationFilter(Filter.LINEAR);
1113
- newColorTexture.create2DFromRaw(cWidth, 2 * numIComps, 3, VtkDataTypes.UNSIGNED_CHAR, cTable);
1114
- model._openGLRenderWindow.setGraphicsResourceForObject(firstColorTransferFunc, newColorTexture, colorFuncHash);
1115
- model.colorTexture = newColorTexture;
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
+ }
1116
1139
  } else {
1117
1140
  model.colorTexture = cTex.oglObject;
1118
1141
  }
1119
- replaceGraphicsResource(model._openGLRenderWindow, model._colorTextureCore, firstColorTransferFunc);
1120
- model._colorTextureCore = firstColorTransferFunc;
1121
-
1122
- // rebuild scalarTextures?
1123
- model.currentValidInputs.forEach((_ref3, component) => {
1124
- let {
1125
- imageData,
1126
- inputIndex
1127
- } = _ref3;
1128
- // rebuild the scalarTexture if the data has changed
1129
- const volumeProperty = volumeProperties[inputIndex];
1130
- const scalars = imageData.getPointData().getScalars();
1131
- const tex = model._openGLRenderWindow.getGraphicsResourceForObject(scalars);
1132
- const scalarsHash = getImageDataHash(imageData, scalars);
1133
- const reBuildTex = !tex?.oglObject?.getHandle() || tex?.hash !== scalarsHash;
1134
- const updatedExtents = volumeProperty.getUpdatedExtents();
1135
- const hasUpdatedExtents = !!updatedExtents.length;
1136
- if (reBuildTex && !hasUpdatedExtents) {
1137
- const newScalarTexture = vtkOpenGLTexture.newInstance();
1138
- newScalarTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1139
- // Build the textures
1140
- const dims = imageData.getDimensions();
1141
- // Use norm16 for scalar texture if the extension is available
1142
- newScalarTexture.setOglNorm16Ext(model.context.getExtension('EXT_texture_norm16'));
1143
- newScalarTexture.resetFormatAndType();
1144
- newScalarTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars, volumeProperty.getPreferSizeOverAccuracy());
1145
- model._openGLRenderWindow.setGraphicsResourceForObject(scalars, newScalarTexture, scalarsHash);
1146
- model.scalarTextures[component] = newScalarTexture;
1147
- } else {
1148
- model.scalarTextures[component] = tex.oglObject;
1149
- }
1150
- if (hasUpdatedExtents) {
1151
- // If hasUpdatedExtents, then the texture is partially updated.
1152
- // clear the array to acknowledge the update.
1153
- volumeProperty.setUpdatedExtents([]);
1154
- const dims = imageData.getDimensions();
1155
- model.scalarTextures[component].create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars, false, updatedExtents);
1156
- }
1157
- replaceGraphicsResource(model._openGLRenderWindow, model._scalarTexturesCore[component], scalars);
1158
- model._scalarTexturesCore[component] = scalars;
1159
- });
1160
-
1161
- // rebuild label outline thickness texture?
1162
- const labelOutlineThicknessArray = firstVolumeProperty.getLabelOutlineThickness();
1163
- const lTex = model._openGLRenderWindow.getGraphicsResourceForObject(labelOutlineThicknessArray);
1164
- const labelOutlineThicknessHash = labelOutlineThicknessArray.join('-');
1165
- const reBuildL = !lTex?.oglObject?.getHandle() || lTex?.hash !== labelOutlineThicknessHash;
1166
- if (reBuildL) {
1167
- const newLabelOutlineThicknessTexture = vtkOpenGLTexture.newInstance();
1168
- newLabelOutlineThicknessTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1169
- let lWidth = model.renderable.getLabelOutlineTextureWidth();
1170
- if (lWidth <= 0) {
1171
- lWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
1172
- }
1173
- const lHeight = 1;
1174
- const lSize = lWidth * lHeight;
1175
- const lTable = new Uint8Array(lSize);
1176
-
1177
- // Assuming labelOutlineThicknessArray contains the thickness for each segment
1178
- for (let i = 0; i < lWidth; ++i) {
1179
- // Retrieve the thickness value for the current segment index.
1180
- // If the value is undefined, use the first element's value as a default, otherwise use the value (even if 0)
1181
- const thickness = typeof labelOutlineThicknessArray[i] !== 'undefined' ? labelOutlineThicknessArray[i] : labelOutlineThicknessArray[0];
1182
- lTable[i] = thickness;
1183
- }
1184
- newLabelOutlineThicknessTexture.resetFormatAndType();
1185
- newLabelOutlineThicknessTexture.setMinificationFilter(Filter.NEAREST);
1186
- newLabelOutlineThicknessTexture.setMagnificationFilter(Filter.NEAREST);
1187
-
1188
- // Create a 2D texture (acting as 1D) from the raw data
1189
- newLabelOutlineThicknessTexture.create2DFromRaw(lWidth, lHeight, 1, VtkDataTypes.UNSIGNED_CHAR, lTable);
1190
- if (labelOutlineThicknessArray) {
1191
- model._openGLRenderWindow.setGraphicsResourceForObject(labelOutlineThicknessArray, newLabelOutlineThicknessTexture, labelOutlineThicknessHash);
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;
1192
1165
  }
1193
- model.labelOutlineThicknessTexture = newLabelOutlineThicknessTexture;
1194
1166
  } else {
1195
- model.labelOutlineThicknessTexture = lTex.oglObject;
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);
1196
1175
  }
1197
- replaceGraphicsResource(model._openGLRenderWindow, model._labelOutlineThicknessTextureCore, labelOutlineThicknessArray);
1198
- model._labelOutlineThicknessTextureCore = labelOutlineThicknessArray;
1199
-
1200
- // rebuild the CABO?
1201
1176
  if (!model.tris.getCABO().getElementCount()) {
1177
+ // build the CABO
1202
1178
  const ptsArray = new Float32Array(12);
1203
1179
  for (let i = 0; i < 4; i++) {
1204
1180
  ptsArray[i * 3] = i % 2 * 2 - 1.0;
@@ -1214,6 +1190,33 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1214
1190
  cellArray[5] = 0;
1215
1191
  cellArray[6] = 3;
1216
1192
  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
+
1217
1220
  const points = vtkDataArray.newInstance({
1218
1221
  numberOfComponents: 3,
1219
1222
  values: ptsArray
@@ -1230,6 +1233,56 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1230
1233
  }
1231
1234
  model.VBOBuildTime.modified();
1232
1235
  };
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
+ };
1233
1286
  }
1234
1287
 
1235
1288
  // ----------------------------------------------------------------------------
@@ -1239,15 +1292,14 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1239
1292
  const DEFAULT_VALUES = {
1240
1293
  context: null,
1241
1294
  VBOBuildTime: null,
1242
- scalarTextures: [],
1243
- _scalarTexturesCore: [],
1295
+ scalarTexture: null,
1244
1296
  opacityTexture: null,
1245
- _opacityTextureCore: null,
1297
+ opacityTextureString: null,
1246
1298
  colorTexture: null,
1247
- _colorTextureCore: null,
1248
- labelOutlineThicknessTexture: null,
1249
- _labelOutlineThicknessTextureCore: null,
1299
+ colorTextureString: null,
1250
1300
  jitterTexture: null,
1301
+ labelOutlineThicknessTexture: null,
1302
+ labelOutlineThicknessTextureString: null,
1251
1303
  tris: null,
1252
1304
  framebuffer: null,
1253
1305
  copyShader: null,
@@ -1256,13 +1308,18 @@ const DEFAULT_VALUES = {
1256
1308
  targetXYF: 1.0,
1257
1309
  zBufferTexture: null,
1258
1310
  lastZBufferTexture: null,
1311
+ lightComplexity: 0,
1259
1312
  fullViewportTime: 1.0,
1260
1313
  idxToView: null,
1261
- vecISToVCMatrix: null,
1314
+ idxNormalMatrix: null,
1262
1315
  modelToView: null,
1263
1316
  projectionToView: null,
1264
1317
  avgWindowArea: 0.0,
1265
1318
  avgFrameTime: 0.0
1319
+ // _scalars: null,
1320
+ // _scalarOpacityFunc: null,
1321
+ // _colorTransferFunc: null,
1322
+ // _labelOutlineThicknessArray: null,
1266
1323
  };
1267
1324
 
1268
1325
  // ----------------------------------------------------------------------------
@@ -1283,6 +1340,11 @@ function extend(publicAPI, model) {
1283
1340
  model.jitterTexture.setWrapS(Wrap.REPEAT);
1284
1341
  model.jitterTexture.setWrapT(Wrap.REPEAT);
1285
1342
  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));
1286
1348
 
1287
1349
  // Build VTK API
1288
1350
  setGet(publicAPI, model, ['context']);