@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.
- package/BREAKING_CHANGES.md +0 -3
- package/Common/Core/ScalarsToColors/Constants.js +7 -2
- package/Common/Core/ScalarsToColors.js +3 -1
- package/Rendering/Core/Actor.d.ts +20 -5
- package/Rendering/Core/Actor.js +68 -5
- package/Rendering/Core/ColorTransferFunction.js +26 -35
- package/Rendering/Core/ImageCPRMapper.d.ts +20 -1
- package/Rendering/Core/ImageCPRMapper.js +2 -1
- package/Rendering/Core/ImageProperty.d.ts +1 -20
- package/Rendering/Core/ImageProperty.js +5 -7
- package/Rendering/Core/ImageResliceMapper.d.ts +20 -1
- package/Rendering/Core/ImageResliceMapper.js +2 -1
- package/Rendering/Core/ImageSlice.d.ts +23 -7
- package/Rendering/Core/ImageSlice.js +68 -9
- package/Rendering/Core/Mapper.js +8 -16
- package/Rendering/Core/Prop3D.d.ts +2 -39
- package/Rendering/Core/Prop3D.js +2 -81
- package/Rendering/Core/ScalarBarActor.js +4 -2
- package/Rendering/Core/Volume.d.ts +20 -5
- package/Rendering/Core/Volume.js +70 -2
- package/Rendering/Core/VolumeMapper/Constants.d.ts +7 -0
- package/Rendering/Core/VolumeMapper/Constants.js +8 -2
- package/Rendering/Core/VolumeMapper.d.ts +173 -16
- package/Rendering/Core/VolumeMapper.js +51 -16
- package/Rendering/Core/VolumeProperty/Constants.d.ts +3 -12
- package/Rendering/Core/VolumeProperty/Constants.js +4 -11
- package/Rendering/Core/VolumeProperty.d.ts +5 -140
- package/Rendering/Core/VolumeProperty.js +7 -54
- package/Rendering/OpenGL/ImageCPRMapper.js +21 -30
- package/Rendering/OpenGL/ImageMapper.js +27 -27
- package/Rendering/OpenGL/ImageResliceMapper.js +183 -271
- package/Rendering/OpenGL/PolyDataMapper.js +8 -1
- package/Rendering/OpenGL/RenderWindow/resourceSharingHelper.d.ts +3 -3
- package/Rendering/OpenGL/RenderWindow/resourceSharingHelper.js +5 -8
- package/Rendering/OpenGL/VolumeMapper.js +784 -722
- package/Rendering/OpenGL/glsl/vtkVolumeFS.glsl.js +1 -1
- package/Rendering/WebGPU/VolumePassFSQ.js +2 -2
- package/index.d.ts +0 -1
- package/macros2.js +1 -1
- package/package.json +1 -1
- 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 {
|
|
16
|
+
import { InterpolationType, OpacityMode, ColorMixPreset } from '../Core/VolumeProperty/Constants.js';
|
|
17
17
|
import { BlendMode } from '../Core/VolumeMapper/Constants.js';
|
|
18
|
-
import {
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
144
|
-
|
|
176
|
+
const vtkImageLabelOutline = publicAPI.isLabelmapOutlineRequired(actor);
|
|
177
|
+
if (vtkImageLabelOutline === true) {
|
|
178
|
+
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::ImageLabelOutlineOn', '#define vtkImageLabelOutlineOn').result;
|
|
145
179
|
}
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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.
|
|
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/
|
|
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.
|
|
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.
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
289
|
-
|
|
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,
|
|
304
|
-
model.previousState =
|
|
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
|
-
|
|
350
|
-
program.setUniformf('sampleDistance',
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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[
|
|
370
|
-
maxVals[
|
|
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[
|
|
373
|
-
maxVals[
|
|
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
|
-
//
|
|
403
|
-
|
|
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
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
program.setUniformf('
|
|
421
|
-
program.setUniformf('
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
//
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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(
|
|
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(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
program.setUniformf(
|
|
593
|
-
|
|
594
|
-
program.setUniformf(
|
|
595
|
-
program.setUniformf(
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
|
701
|
-
const
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
program.setUniformf(
|
|
723
|
-
program.setUniformf(
|
|
724
|
-
program.setUniformf(
|
|
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
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
897
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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) =>
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
|
1037
|
+
const oTable = new Uint8Array(32 * 32);
|
|
1011
1038
|
for (let i = 0; i < 32 * 32; ++i) {
|
|
1012
|
-
|
|
1039
|
+
oTable[i] = 255.0 * Math.random();
|
|
1013
1040
|
}
|
|
1014
|
-
model.jitterTexture.setMinificationFilter(Filter.
|
|
1015
|
-
model.jitterTexture.setMagnificationFilter(Filter.
|
|
1016
|
-
model.jitterTexture.create2DFromRaw(32, 32, 1, VtkDataTypes.
|
|
1017
|
-
}
|
|
1018
|
-
const
|
|
1019
|
-
const
|
|
1020
|
-
const
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1023
|
-
|
|
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
|
-
|
|
1036
|
-
|
|
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 =
|
|
1046
|
-
const opacityFactor = publicAPI.getCurrentSampleDistance(ren) /
|
|
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
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1089
|
+
model.opacityTexture.create2DFromRaw(oWidth, 2 * numIComps, 1, VtkDataTypes.UNSIGNED_CHAR, oTable);
|
|
1071
1090
|
}
|
|
1072
|
-
if (
|
|
1073
|
-
model._openGLRenderWindow.setGraphicsResourceForObject(
|
|
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
|
|
1084
|
-
|
|
1085
|
-
|
|
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
|
-
|
|
1093
|
-
|
|
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 =
|
|
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
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
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.
|
|
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
|
-
|
|
1243
|
-
_scalarTexturesCore: [],
|
|
1295
|
+
scalarTexture: null,
|
|
1244
1296
|
opacityTexture: null,
|
|
1245
|
-
|
|
1297
|
+
opacityTextureString: null,
|
|
1246
1298
|
colorTexture: null,
|
|
1247
|
-
|
|
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
|
-
|
|
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']);
|