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