@kitware/vtk.js 29.7.2 → 29.8.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/IO/Core/HttpDataSetReader.js +4 -2
- package/Rendering/Core/ImageCPRMapper/Constants.d.ts +10 -0
- package/Rendering/Core/ImageCPRMapper/Constants.js +10 -0
- package/Rendering/Core/ImageCPRMapper.d.ts +46 -0
- package/Rendering/Core/ImageCPRMapper.js +11 -3
- package/Rendering/OpenGL/Framebuffer.js +2 -6
- package/Rendering/OpenGL/HardwareSelector.js +1 -1
- package/Rendering/OpenGL/ImageCPRMapper.js +154 -76
- package/Rendering/OpenGL/RenderWindow.js +2 -4
- package/Rendering/OpenGL/SurfaceLIC/LineIntegralConvolution2D.js +2 -1
- package/Rendering/OpenGL/VolumeMapper.js +1 -1
- package/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -185,8 +185,10 @@ function vtkHttpDataSetReader(publicAPI, model) {
|
|
|
185
185
|
// cacheArrays[arrayId] can be a promise or value
|
|
186
186
|
Promise.resolve(cachedArraysAndPromises[arrayId]).then(cachedArray => {
|
|
187
187
|
if (array !== cachedArray) {
|
|
188
|
-
// Update last access for cache retention rules
|
|
189
|
-
|
|
188
|
+
// Update last access for cache retention rules (if caching is enabled)
|
|
189
|
+
if (model.maxCacheSize) {
|
|
190
|
+
cachedArraysMetaData[arrayId].lastAccess = new Date();
|
|
191
|
+
}
|
|
190
192
|
|
|
191
193
|
// Assign cached array as result
|
|
192
194
|
Object.assign(array, cachedArray);
|
|
@@ -6,6 +6,7 @@ import vtkDataArray from './../../Common/Core/DataArray';
|
|
|
6
6
|
import vtkImageData from './../../Common/DataModel/ImageData';
|
|
7
7
|
import vtkPolyData from './../../Common/DataModel/PolyData';
|
|
8
8
|
import vtkPolyLine from './../../Common/DataModel/PolyLine';
|
|
9
|
+
import { ProjectionMode } from './ImageCPRMapper/Constants';
|
|
9
10
|
|
|
10
11
|
interface ICoincidentTopology {
|
|
11
12
|
factor: number;
|
|
@@ -157,6 +158,51 @@ export interface vtkImageCPRMapper extends vtkAbstractMapper3D {
|
|
|
157
158
|
*/
|
|
158
159
|
setDirectionMatrix(mat: mat3): boolean;
|
|
159
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Thickness of the projection slab in image coordinates (NOT in voxels)
|
|
163
|
+
* Usually in millimeters if the spacing of the input image is set from a DICOM
|
|
164
|
+
*/
|
|
165
|
+
getProjectionSlabThickness(): number;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @see getProjectionSlabThickness
|
|
169
|
+
* @param projectionSlabThickness
|
|
170
|
+
*/
|
|
171
|
+
setProjectionSlabThickness(ProjectionSlabThickness: number): boolean;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Total number of samples of the volume done by the projection mode
|
|
175
|
+
* If this number is equal or less than 1, projection is disabled
|
|
176
|
+
* Using an odd number is advised
|
|
177
|
+
* If this number is even, the center of the slab will not be sampled
|
|
178
|
+
*/
|
|
179
|
+
getProjectionSlabNumberOfSamples(): number;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @see getProjectionSlabNumberOfSamples
|
|
183
|
+
* @param projectionSlabNumberOfSamples
|
|
184
|
+
*/
|
|
185
|
+
setProjectionSlabNumberOfSamples(projectionSlabNumberOfSamples: number): boolean;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Returns wether projection is enabled
|
|
189
|
+
* It is based on the number of samples
|
|
190
|
+
* @see getProjectionSlabNumberOfSamples
|
|
191
|
+
*/
|
|
192
|
+
isProjectionEnabled(): boolean;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The different modes of projection
|
|
196
|
+
* Available modes include MIP, MinIP and AverageIP
|
|
197
|
+
*/
|
|
198
|
+
getProjectionMode(): ProjectionMode;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @see getProjectionMode
|
|
202
|
+
* @param projectionMode
|
|
203
|
+
*/
|
|
204
|
+
setProjectionMode(projectionMode: ProjectionMode): boolean;
|
|
205
|
+
|
|
160
206
|
/**
|
|
161
207
|
* Find the data array to use for orientation in the input polydata ( @see getOrientationArrayName )
|
|
162
208
|
*/
|
|
@@ -4,6 +4,7 @@ import vtkAbstractImageMapper from './AbstractImageMapper.js';
|
|
|
4
4
|
import { m as macro } from '../../macros2.js';
|
|
5
5
|
import vtkPoints from '../../Common/Core/Points.js';
|
|
6
6
|
import vtkPolyLine from '../../Common/DataModel/PolyLine.js';
|
|
7
|
+
import { ProjectionMode } from './ImageCPRMapper/Constants.js';
|
|
7
8
|
|
|
8
9
|
const {
|
|
9
10
|
vtkErrorMacro
|
|
@@ -88,7 +89,10 @@ function vtkImageCPRMapper(publicAPI, model) {
|
|
|
88
89
|
const numComps = model.useUniformOrientation ? model.uniformOrientation.length : orientationDataArray.getNumberOfComponents();
|
|
89
90
|
switch (numComps) {
|
|
90
91
|
case 16:
|
|
91
|
-
convert =
|
|
92
|
+
convert = (outQuat, inMat) => {
|
|
93
|
+
mat4.getRotation(outQuat, inMat);
|
|
94
|
+
quat.normalize(outQuat, outQuat);
|
|
95
|
+
};
|
|
92
96
|
break;
|
|
93
97
|
case 9:
|
|
94
98
|
convert = (outQuat, inMat) => {
|
|
@@ -250,6 +254,7 @@ function vtkImageCPRMapper(publicAPI, model) {
|
|
|
250
254
|
return Math.sqrt(d2 - x * x);
|
|
251
255
|
});
|
|
252
256
|
};
|
|
257
|
+
publicAPI.isProjectionEnabled = () => model.projectionSlabNumberOfSamples > 1;
|
|
253
258
|
publicAPI.setCenterlineData = centerlineData => publicAPI.setInputData(centerlineData, 1);
|
|
254
259
|
publicAPI.setCenterlineConnection = centerlineConnection => publicAPI.setInputConnection(centerlineConnection, 1);
|
|
255
260
|
publicAPI.setImageData = imageData => publicAPI.setInputData(imageData, 0);
|
|
@@ -274,7 +279,10 @@ const DEFAULT_VALUES = {
|
|
|
274
279
|
orientationArrayName: null,
|
|
275
280
|
tangentDirection: [1, 0, 0],
|
|
276
281
|
bitangentDirection: [0, 1, 0],
|
|
277
|
-
normalDirection: [0, 0, 1]
|
|
282
|
+
normalDirection: [0, 0, 1],
|
|
283
|
+
projectionSlabThickness: 1,
|
|
284
|
+
projectionSlabNumberOfSamples: 1,
|
|
285
|
+
projectionMode: ProjectionMode.MAX
|
|
278
286
|
};
|
|
279
287
|
|
|
280
288
|
// ----------------------------------------------------------------------------
|
|
@@ -294,7 +302,7 @@ function extend(publicAPI, model) {
|
|
|
294
302
|
});
|
|
295
303
|
|
|
296
304
|
// Setters and getters
|
|
297
|
-
macro.setGet(publicAPI, model, ['width', 'uniformOrientation', 'useUniformOrientation', 'centerPoint', 'preferSizeOverAccuracy', 'orientationArrayName', 'tangentDirection', 'bitangentDirection', 'normalDirection']);
|
|
305
|
+
macro.setGet(publicAPI, model, ['width', 'uniformOrientation', 'useUniformOrientation', 'centerPoint', 'preferSizeOverAccuracy', 'orientationArrayName', 'tangentDirection', 'bitangentDirection', 'normalDirection', 'projectionSlabThickness', 'projectionSlabNumberOfSamples', 'projectionMode']);
|
|
298
306
|
CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);
|
|
299
307
|
|
|
300
308
|
// Object methods
|
|
@@ -148,12 +148,8 @@ function vtkFramebuffer(publicAPI, model) {
|
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
publicAPI.getSize = () => {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
size[0] = model.glFramebuffer.width;
|
|
154
|
-
size[1] = model.glFramebuffer.height;
|
|
155
|
-
}
|
|
156
|
-
return size;
|
|
151
|
+
if (model.glFramebuffer == null) return null;
|
|
152
|
+
return [model.glFramebuffer.width, model.glFramebuffer.height];
|
|
157
153
|
};
|
|
158
154
|
publicAPI.populateFramebuffer = () => {
|
|
159
155
|
if (!model.context) {
|
|
@@ -241,7 +241,7 @@ function vtkOpenGLHardwareSelector(publicAPI, model) {
|
|
|
241
241
|
model.framebuffer.setOpenGLRenderWindow(model._openGLRenderWindow);
|
|
242
242
|
model.framebuffer.saveCurrentBindingsAndBuffers();
|
|
243
243
|
const fbSize = model.framebuffer.getSize();
|
|
244
|
-
if (fbSize[0] !== size[0] || fbSize[1] !== size[1]) {
|
|
244
|
+
if (!fbSize || fbSize[0] !== size[0] || fbSize[1] !== size[1]) {
|
|
245
245
|
model.framebuffer.create(size[0], size[1]);
|
|
246
246
|
// this calls model.framebuffer.bind()
|
|
247
247
|
model.framebuffer.populateFramebuffer();
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { m as macro } from '../../macros2.js';
|
|
2
2
|
import { mat4, vec3 } from 'gl-matrix';
|
|
3
|
-
import
|
|
3
|
+
import { Filter } from './Texture/Constants.js';
|
|
4
|
+
import { InterpolationType } from '../Core/ImageProperty/Constants.js';
|
|
5
|
+
import { ProjectionMode } from '../Core/ImageCPRMapper/Constants.js';
|
|
6
|
+
import { Representation } from '../Core/Property/Constants.js';
|
|
7
|
+
import { VtkDataTypes } from '../../Common/Core/DataArray/Constants.js';
|
|
8
|
+
import vtkDataArray from '../../Common/Core/DataArray.js';
|
|
4
9
|
import vtkHelper from './Helper.js';
|
|
10
|
+
import vtkOpenGLTexture from './Texture.js';
|
|
5
11
|
import vtkReplacementShaderMapper from './ReplacementShaderMapper.js';
|
|
6
12
|
import vtkShaderProgram from './ShaderProgram.js';
|
|
7
|
-
import
|
|
8
|
-
import vtkDataArray from '../../Common/Core/DataArray.js';
|
|
9
|
-
import { VtkDataTypes } from '../../Common/Core/DataArray/Constants.js';
|
|
10
|
-
import { Representation } from '../Core/Property/Constants.js';
|
|
11
|
-
import { Filter } from './Texture/Constants.js';
|
|
12
|
-
import { InterpolationType } from '../Core/ImageProperty/Constants.js';
|
|
13
|
+
import vtkViewNode from '../SceneGraph/ViewNode.js';
|
|
13
14
|
import { v as vtkPolyDataVS } from './glsl/vtkPolyDataVS.glsl.js';
|
|
14
15
|
import { v as vtkPolyDataFS } from './glsl/vtkPolyDataFS.glsl.js';
|
|
15
16
|
import { registerOverride } from './ViewNodeFactory.js';
|
|
@@ -364,40 +365,35 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
364
365
|
});
|
|
365
366
|
const customAttributes = [centerlinePosition, quadIndex];
|
|
366
367
|
if (!model.renderable.getUseUniformOrientation()) {
|
|
367
|
-
// For each
|
|
368
|
-
// Send
|
|
369
|
-
// The interpolation will occur in the fragment shader (slerp)
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
for (let
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
for (let
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// Bot array
|
|
384
|
-
centerlineBotDirectionArray[offset + 0] = directions[baseDirectionIdx + 3];
|
|
385
|
-
centerlineBotDirectionArray[offset + 1] = directions[baseDirectionIdx + 4];
|
|
386
|
-
centerlineBotDirectionArray[offset + 2] = directions[baseDirectionIdx + 5];
|
|
387
|
-
offset += 3;
|
|
368
|
+
// For each quad (i.e. centerline segment), a top and bottom quaternion give the orientation
|
|
369
|
+
// Send both quaternions to each vertex and use flat interpolation to get them "as is" in the fragment shader
|
|
370
|
+
// The interpolation of the quaternions will occur in the fragment shader (slerp)
|
|
371
|
+
const orientationQuats = model.renderable.getOrientedCenterline().getOrientations() ?? [];
|
|
372
|
+
const centerlineTopOrientationArray = new Float32Array(4 * nPts);
|
|
373
|
+
const centerlineBotOrientationArray = new Float32Array(4 * nPts);
|
|
374
|
+
for (let quadIdx = 0; quadIdx < nLines; ++quadIdx) {
|
|
375
|
+
// All vertices of a given quad have the same topDir and botDir
|
|
376
|
+
// Polyline goes from top to bottom
|
|
377
|
+
const topQuat = orientationQuats[quadIdx];
|
|
378
|
+
const botQuat = orientationQuats[quadIdx + 1];
|
|
379
|
+
for (let pointInQuadIdx = 0; pointInQuadIdx < 4; ++pointInQuadIdx) {
|
|
380
|
+
const pointIdx = pointInQuadIdx + 4 * quadIdx;
|
|
381
|
+
const quaternionArrayOffset = 4 * pointIdx;
|
|
382
|
+
centerlineTopOrientationArray.set(topQuat, quaternionArrayOffset);
|
|
383
|
+
centerlineBotOrientationArray.set(botQuat, quaternionArrayOffset);
|
|
388
384
|
}
|
|
389
385
|
}
|
|
390
|
-
const
|
|
391
|
-
numberOfComponents:
|
|
392
|
-
values:
|
|
393
|
-
name: '
|
|
386
|
+
const centerlineTopOrientation = vtkDataArray.newInstance({
|
|
387
|
+
numberOfComponents: 4,
|
|
388
|
+
values: centerlineTopOrientationArray,
|
|
389
|
+
name: 'centerlineTopOrientation'
|
|
394
390
|
});
|
|
395
|
-
const
|
|
396
|
-
numberOfComponents:
|
|
397
|
-
values:
|
|
398
|
-
name: '
|
|
391
|
+
const centerlineBotOrientation = vtkDataArray.newInstance({
|
|
392
|
+
numberOfComponents: 4,
|
|
393
|
+
values: centerlineBotOrientationArray,
|
|
394
|
+
name: 'centerlineBotOrientation'
|
|
399
395
|
});
|
|
400
|
-
customAttributes.push(
|
|
396
|
+
customAttributes.push(centerlineTopOrientation, centerlineBotOrientation);
|
|
401
397
|
}
|
|
402
398
|
model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, {
|
|
403
399
|
points,
|
|
@@ -420,9 +416,11 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
420
416
|
const iComp = actor.getProperty().getIndependentComponents();
|
|
421
417
|
const useCenterPoint = !!model.renderable.getCenterPoint();
|
|
422
418
|
const useUniformOrientation = model.renderable.getUseUniformOrientation();
|
|
423
|
-
|
|
419
|
+
const projectionMode = model.renderable.isProjectionEnabled() && model.renderable.getProjectionMode();
|
|
420
|
+
if (cellBO.getProgram() === 0 || model.lastUseCenterPoint !== useCenterPoint || model.lastUseUniformOrientation !== useUniformOrientation || model.lastProjectionMode !== projectionMode || model.lastHaveSeenDepthRequest !== model.haveSeenDepthRequest || model.lastTextureComponents !== tNumComp || model.lastIndependentComponents !== iComp) {
|
|
424
421
|
model.lastUseCenterPoint = useCenterPoint;
|
|
425
422
|
model.lastUseUniformOrientation = useUniformOrientation;
|
|
423
|
+
model.lastProjectionMode = projectionMode;
|
|
426
424
|
model.lastHaveSeenDepthRequest = model.haveSeenDepthRequest;
|
|
427
425
|
model.lastTextureComponents = tNumComp;
|
|
428
426
|
model.lastIndependentComponents = iComp;
|
|
@@ -438,15 +436,22 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
438
436
|
let VSSource = shaders.Vertex;
|
|
439
437
|
let FSSource = shaders.Fragment;
|
|
440
438
|
|
|
439
|
+
// https://glmatrix.net/docs/vec3.js.html#line522
|
|
440
|
+
const applyQuaternionToVecShaderFunction = ['vec3 applyQuaternionToVec(vec4 q, vec3 v) {', ' float uvx = q.y * v.z - q.z * v.y;', ' float uvy = q.z * v.x - q.x * v.z;', ' float uvz = q.x * v.y - q.y * v.x;', ' float uuvx = q.y * uvz - q.z * uvy;', ' float uuvy = q.z * uvx - q.x * uvz;', ' float uuvz = q.x * uvy - q.y * uvx;', ' float w2 = q.w * 2.0;', ' uvx *= w2;', ' uvy *= w2;', ' uvz *= w2;', ' uuvx *= 2.0;', ' uuvy *= 2.0;', ' uuvz *= 2.0;', ' return vec3(v.x + uvx + uuvx, v.y + uvy + uuvy, v.z + uvz + uuvz);', '}'];
|
|
441
|
+
|
|
441
442
|
// Vertex shader main replacements
|
|
442
443
|
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', ['uniform mat4 MCPCMatrix;']).result;
|
|
443
444
|
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::PositionVC::Impl', [' gl_Position = MCPCMatrix * vertexMC;']).result;
|
|
444
445
|
const vsColorDec = ['attribute vec3 centerlinePosition;', 'attribute float quadIndex;', 'uniform float width;', 'out vec2 quadOffsetVSOutput;', 'out vec3 centerlinePosVSOutput;'];
|
|
446
|
+
const useProjection = model.renderable.isProjectionEnabled();
|
|
445
447
|
const isDirectionUniform = model.renderable.getUseUniformOrientation();
|
|
446
448
|
if (isDirectionUniform) {
|
|
447
|
-
vsColorDec.push('out vec3
|
|
449
|
+
vsColorDec.push('out vec3 samplingDirVSOutput;', 'uniform vec4 centerlineOrientation;', 'uniform vec3 tangentDirection;', ...applyQuaternionToVecShaderFunction);
|
|
450
|
+
if (useProjection) {
|
|
451
|
+
vsColorDec.push('out vec3 projectionDirVSOutput;', 'uniform vec3 bitangentDirection;');
|
|
452
|
+
}
|
|
448
453
|
} else {
|
|
449
|
-
vsColorDec.push('out
|
|
454
|
+
vsColorDec.push('out vec4 centerlineTopOrientationVSOutput;', 'out vec4 centerlineBotOrientationVSOutput;', 'attribute vec4 centerlineTopOrientation;', 'attribute vec4 centerlineBotOrientation;');
|
|
450
455
|
}
|
|
451
456
|
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Dec', vsColorDec).result;
|
|
452
457
|
const vsColorImpl = [
|
|
@@ -454,15 +459,12 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
454
459
|
// quadOffsetVSOutput.y: bottom = 0.0; top = 1.0;
|
|
455
460
|
'quadOffsetVSOutput = vec2(width * (mod(quadIndex, 2.0) == 0.0 ? -0.5 : 0.5), quadIndex > 1.0 ? 0.0 : 1.0);', 'centerlinePosVSOutput = centerlinePosition;'];
|
|
456
461
|
if (isDirectionUniform) {
|
|
457
|
-
vsColorImpl.push('
|
|
462
|
+
vsColorImpl.push('samplingDirVSOutput = applyQuaternionToVec(centerlineOrientation, tangentDirection);');
|
|
463
|
+
if (useProjection) {
|
|
464
|
+
vsColorImpl.push('projectionDirVSOutput = applyQuaternionToVec(centerlineOrientation, bitangentDirection);');
|
|
465
|
+
}
|
|
458
466
|
} else {
|
|
459
|
-
vsColorImpl.push(
|
|
460
|
-
// When u and v are unit vectors: uvAngle = 2 * atan2(|| u - v ||, || u + v ||)
|
|
461
|
-
// When u != -v: || u + v || > 0
|
|
462
|
-
// When x > 0: atan2(y, x) = atan(y/x)
|
|
463
|
-
// Thus: dirAngle = 2 * atan(|| topDir - botDir || / || topDir + botDir ||)
|
|
464
|
-
// This is more stable and should not be to slow compared to acos(dot(u, v))
|
|
465
|
-
'vec3 sumVec = centerlineTopDirection + centerlineBotDirection;', 'float sumLen2 = dot(sumVec, sumVec);', 'float diffLen2 = 4.0 - sumLen2;', 'if (diffLen2 < 0.001) {', ' // vectors are too close to each other, use lerp', ' centerlineAngleVSOutput = -1.0; // use negative angle as a flag for lerp', ' centerlineTopDirVSOutput = centerlineTopDirection;', ' centerlineBotDirVSOutput = centerlineBotDirection;', '} else if (sumLen2 == 0.0) {', " // vector are opposite to each other, don't make a choice for the user", ' // use slerp without direction, it will display the centerline color on each row of pixel', ' centerlineAngleVSOutput = 0.0;', ' centerlineTopDirVSOutput = vec3(0.0);', ' centerlineBotDirVSOutput = vec3(0.0);', '} else {', ' // use slerp', ' centerlineAngleVSOutput = 2.0 * atan(sqrt(diffLen2/sumLen2));', ' float sinAngle = sin(centerlineAngleVSOutput);', ' centerlineTopDirVSOutput = centerlineTopDirection / sinAngle;', ' centerlineBotDirVSOutput = centerlineBotDirection / sinAngle;', '}');
|
|
467
|
+
vsColorImpl.push('centerlineTopOrientationVSOutput = centerlineTopOrientation;', 'centerlineBotOrientationVSOutput = centerlineBotOrientation;');
|
|
466
468
|
}
|
|
467
469
|
VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Color::Impl', vsColorImpl).result;
|
|
468
470
|
|
|
@@ -484,10 +486,19 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
484
486
|
`uniform float cshift0;`, `uniform float cscale0;`,
|
|
485
487
|
// weighting shift and scale
|
|
486
488
|
`uniform float pwfshift0;`, `uniform float pwfscale0;`];
|
|
489
|
+
if (useProjection) {
|
|
490
|
+
tcoordFSDec.push('uniform vec3 spacing;', 'uniform int projectionSlabNumberOfSamples;', 'uniform float projectionConstantOffset;', 'uniform float projectionStepLength;');
|
|
491
|
+
}
|
|
487
492
|
if (isDirectionUniform) {
|
|
488
|
-
tcoordFSDec.push('in vec3
|
|
493
|
+
tcoordFSDec.push('in vec3 samplingDirVSOutput;');
|
|
494
|
+
if (useProjection) {
|
|
495
|
+
tcoordFSDec.push('in vec3 projectionDirVSOutput;');
|
|
496
|
+
}
|
|
489
497
|
} else {
|
|
490
|
-
tcoordFSDec.push('
|
|
498
|
+
tcoordFSDec.push('uniform vec3 tangentDirection;', 'in vec4 centerlineTopOrientationVSOutput;', 'in vec4 centerlineBotOrientationVSOutput;', ...applyQuaternionToVecShaderFunction);
|
|
499
|
+
if (useProjection) {
|
|
500
|
+
tcoordFSDec.push('uniform vec3 bitangentDirection;');
|
|
501
|
+
}
|
|
491
502
|
}
|
|
492
503
|
const centerPoint = model.renderable.getCenterPoint();
|
|
493
504
|
if (centerPoint) {
|
|
@@ -526,18 +537,62 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
526
537
|
FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::TCoord::Dec', tcoordFSDec).result;
|
|
527
538
|
let tcoordFSImpl = [];
|
|
528
539
|
if (isDirectionUniform) {
|
|
529
|
-
tcoordFSImpl.push('vec3
|
|
540
|
+
tcoordFSImpl.push('vec3 samplingDirection = samplingDirVSOutput;');
|
|
541
|
+
if (useProjection) {
|
|
542
|
+
tcoordFSImpl.push('vec3 projectionDirection = projectionDirVSOutput;');
|
|
543
|
+
}
|
|
530
544
|
} else {
|
|
531
545
|
// Slerp or lerp between centerlineTopDirVSOutput and centerlineBotDirVSOutput
|
|
532
546
|
// We use quadOffsetVSOutput.y: bottom = 0.0; top = 1.0;
|
|
533
|
-
tcoordFSImpl.push(
|
|
547
|
+
tcoordFSImpl.push(
|
|
548
|
+
// Slerp / Lerp
|
|
549
|
+
'vec4 q0 = centerlineBotOrientationVSOutput;', 'vec4 q1 = centerlineTopOrientationVSOutput;', 'float qCosAngle = dot(q0, q1);', 'vec4 interpolatedOrientation;', 'if (qCosAngle > 0.999 || qCosAngle < -0.999) {', ' // Use LERP instead of SLERP when the two quaternions are close or opposite', ' interpolatedOrientation = normalize(mix(q0, q1, quadOffsetVSOutput.y));', '} else {', ' float omega = acos(qCosAngle);', ' interpolatedOrientation = normalize(sin((1.0 - quadOffsetVSOutput.y) * omega) * q0 + sin(quadOffsetVSOutput.y * omega) * q1);', '}', 'vec3 samplingDirection = applyQuaternionToVec(interpolatedOrientation, tangentDirection);');
|
|
550
|
+
if (useProjection) {
|
|
551
|
+
tcoordFSImpl.push('vec3 projectionDirection = applyQuaternionToVec(interpolatedOrientation, bitangentDirection);');
|
|
552
|
+
}
|
|
534
553
|
}
|
|
535
554
|
if (centerPoint) {
|
|
536
|
-
tcoordFSImpl.push('float baseOffset = dot(
|
|
555
|
+
tcoordFSImpl.push('float baseOffset = dot(samplingDirection, globalCenterPoint - centerlinePosVSOutput);', 'float horizontalOffset = quadOffsetVSOutput.x + baseOffset;');
|
|
537
556
|
} else {
|
|
538
557
|
tcoordFSImpl.push('float horizontalOffset = quadOffsetVSOutput.x;');
|
|
539
558
|
}
|
|
540
|
-
tcoordFSImpl.push('vec3 volumePosMC = centerlinePosVSOutput + horizontalOffset *
|
|
559
|
+
tcoordFSImpl.push('vec3 volumePosMC = centerlinePosVSOutput + horizontalOffset * samplingDirection;', 'vec3 volumePosTC = (MCTCMatrix * vec4(volumePosMC, 1.0)).xyz;', 'if (any(lessThan(volumePosTC, vec3(0.0))) || any(greaterThan(volumePosTC, vec3(1.0))))', '{', ' // set the background color and exit', ' gl_FragData[0] = backgroundColor;', ' return;', '}');
|
|
560
|
+
if (useProjection) {
|
|
561
|
+
const projectionMode = model.renderable.getProjectionMode();
|
|
562
|
+
switch (projectionMode) {
|
|
563
|
+
case ProjectionMode.MIN:
|
|
564
|
+
tcoordFSImpl.push('const vec4 initialProjectionTextureValue = vec4(1.0);');
|
|
565
|
+
break;
|
|
566
|
+
case ProjectionMode.MAX:
|
|
567
|
+
case ProjectionMode.AVERAGE:
|
|
568
|
+
default:
|
|
569
|
+
tcoordFSImpl.push('const vec4 initialProjectionTextureValue = vec4(0.0);');
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Loop on all the samples of the projection
|
|
574
|
+
tcoordFSImpl.push('vec3 projectionScaledDirection = projectionDirection / spacing;', 'vec3 projectionStep = projectionStepLength * projectionScaledDirection;', 'vec3 projectionStartPosition = volumePosTC + projectionConstantOffset * projectionScaledDirection;', 'vec4 tvalue = initialProjectionTextureValue;', 'for (int projectionSampleIdx = 0; projectionSampleIdx < projectionSlabNumberOfSamples; ++projectionSampleIdx) {', ' vec3 projectionSamplePosition = projectionStartPosition + float(projectionSampleIdx) * projectionStep;', ' vec4 sampledTextureValue = texture(volumeTexture, projectionSamplePosition);');
|
|
575
|
+
switch (projectionMode) {
|
|
576
|
+
case ProjectionMode.MAX:
|
|
577
|
+
tcoordFSImpl.push(' tvalue = max(tvalue, sampledTextureValue);');
|
|
578
|
+
break;
|
|
579
|
+
case ProjectionMode.MIN:
|
|
580
|
+
tcoordFSImpl.push(' tvalue = min(tvalue, sampledTextureValue);');
|
|
581
|
+
break;
|
|
582
|
+
case ProjectionMode.AVERAGE:
|
|
583
|
+
default:
|
|
584
|
+
tcoordFSImpl.push(' tvalue = tvalue + sampledTextureValue;');
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
tcoordFSImpl.push('}');
|
|
588
|
+
|
|
589
|
+
// Process the total if needed
|
|
590
|
+
if (projectionMode === ProjectionMode.AVERAGE) {
|
|
591
|
+
tcoordFSImpl.push('tvalue = tvalue / float(projectionSlabNumberOfSamples);');
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
tcoordFSImpl.push('vec4 tvalue = texture(volumeTexture, volumePosTC);');
|
|
595
|
+
}
|
|
541
596
|
if (iComps) {
|
|
542
597
|
const rgba = ['r', 'g', 'b', 'a'];
|
|
543
598
|
for (let comp = 0; comp < tNumComp; ++comp) {
|
|
@@ -610,32 +665,55 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
610
665
|
shaders.Geometry = '';
|
|
611
666
|
};
|
|
612
667
|
publicAPI.setMapperShaderParameters = (cellBO, ren, actor) => {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
668
|
+
const program = cellBO.getProgram();
|
|
669
|
+
const cellArrayBufferObject = cellBO.getCABO();
|
|
670
|
+
if (cellArrayBufferObject.getElementCount() && (model.VBOBuildTime.getMTime() > cellBO.getAttributeUpdateTime().getMTime() || cellBO.getShaderSourceTime().getMTime() > cellBO.getAttributeUpdateTime().getMTime())) {
|
|
671
|
+
if (program.isAttributeUsed('vertexMC')) {
|
|
672
|
+
if (!cellBO.getVAO().addAttributeArray(program, cellArrayBufferObject, 'vertexMC', cellArrayBufferObject.getVertexOffset(), cellArrayBufferObject.getStride(), model.context.FLOAT, 3, model.context.FALSE)) {
|
|
616
673
|
vtkErrorMacro('Error setting vertexMC in shader VAO.');
|
|
617
674
|
}
|
|
618
675
|
}
|
|
619
676
|
// Custom data of the CABO (centerlinePosition, centerlineTopDirection,
|
|
620
677
|
// centerlineBotDirection, quadIndex and user defined custom data)
|
|
621
678
|
cellBO.getCABO().getCustomData().forEach(data => {
|
|
622
|
-
if (data &&
|
|
679
|
+
if (data && program.isAttributeUsed(data.name) && !cellBO.getVAO().addAttributeArray(program, cellArrayBufferObject, data.name, data.offset, cellArrayBufferObject.getStride(), model.context.FLOAT, data.components, model.context.FALSE)) {
|
|
623
680
|
vtkErrorMacro(`Error setting ${data.name} in shader VAO.`);
|
|
624
681
|
}
|
|
625
682
|
});
|
|
626
683
|
cellBO.getAttributeUpdateTime().modified();
|
|
627
684
|
}
|
|
628
685
|
const texUnit = model.volumeTexture.getTextureUnit();
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
cellBO.getProgram().
|
|
632
|
-
if (
|
|
633
|
-
const
|
|
634
|
-
cellBO.getProgram().setUniform3fArray('
|
|
635
|
-
}
|
|
636
|
-
if (
|
|
686
|
+
program.setUniformi('volumeTexture', texUnit);
|
|
687
|
+
program.setUniformf('width', model.renderable.getWidth());
|
|
688
|
+
cellBO.getProgram().setUniform4fv('backgroundColor', model.renderable.getBackgroundColor());
|
|
689
|
+
if (program.isUniformUsed('tangentDirection')) {
|
|
690
|
+
const tangentDirection = model.renderable.getTangentDirection();
|
|
691
|
+
cellBO.getProgram().setUniform3fArray('tangentDirection', tangentDirection);
|
|
692
|
+
}
|
|
693
|
+
if (program.isUniformUsed('bitangentDirection')) {
|
|
694
|
+
const bitangentDirection = model.renderable.getBitangentDirection();
|
|
695
|
+
cellBO.getProgram().setUniform3fArray('bitangentDirection', bitangentDirection);
|
|
696
|
+
}
|
|
697
|
+
if (program.isUniformUsed('centerlineOrientation')) {
|
|
698
|
+
const uniformOrientation = model.renderable.getUniformOrientation();
|
|
699
|
+
cellBO.getProgram().setUniform4fv('centerlineOrientation', uniformOrientation);
|
|
700
|
+
}
|
|
701
|
+
if (program.isUniformUsed('globalCenterPoint')) {
|
|
637
702
|
const centerPoint = model.renderable.getCenterPoint();
|
|
638
|
-
|
|
703
|
+
program.setUniform3fArray('globalCenterPoint', centerPoint);
|
|
704
|
+
}
|
|
705
|
+
// Projection uniforms
|
|
706
|
+
if (model.renderable.isProjectionEnabled()) {
|
|
707
|
+
const image = model.currentImageDataInput;
|
|
708
|
+
const spacing = image.getSpacing();
|
|
709
|
+
const projectionSlabThickness = model.renderable.getProjectionSlabThickness();
|
|
710
|
+
const projectionSlabNumberOfSamples = model.renderable.getProjectionSlabNumberOfSamples();
|
|
711
|
+
program.setUniform3fArray('spacing', spacing);
|
|
712
|
+
program.setUniformi('projectionSlabNumberOfSamples', projectionSlabNumberOfSamples);
|
|
713
|
+
const constantOffset = -0.5 * projectionSlabThickness;
|
|
714
|
+
program.setUniformf('projectionConstantOffset', constantOffset);
|
|
715
|
+
const stepLength = projectionSlabThickness / (projectionSlabNumberOfSamples - 1);
|
|
716
|
+
program.setUniformf('projectionStepLength', stepLength);
|
|
639
717
|
}
|
|
640
718
|
|
|
641
719
|
// Model coordinates to image space
|
|
@@ -646,7 +724,7 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
646
724
|
const MCICMatrix = image.getWorldToIndex();
|
|
647
725
|
const ICTCMatrix = mat4.fromScaling(new Float32Array(16), vec3.inverse([], image.getDimensions()));
|
|
648
726
|
const MCTCMatrix = mat4.mul(ICTCMatrix, ICTCMatrix, MCICMatrix);
|
|
649
|
-
|
|
727
|
+
program.setUniformMatrix('MCTCMatrix', MCTCMatrix);
|
|
650
728
|
if (model.haveSeenDepthRequest) {
|
|
651
729
|
cellBO.getProgram().setUniformi('depthRequest', model.renderDepth ? 1 : 0);
|
|
652
730
|
}
|
|
@@ -657,8 +735,8 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
657
735
|
macro.vtkErrorMacro('OpenGL has a limit of 6 clipping planes');
|
|
658
736
|
numClipPlanes = 6;
|
|
659
737
|
}
|
|
660
|
-
const shiftScaleEnabled =
|
|
661
|
-
const inverseShiftScaleMatrix = shiftScaleEnabled ?
|
|
738
|
+
const shiftScaleEnabled = cellArrayBufferObject.getCoordShiftAndScaleEnabled();
|
|
739
|
+
const inverseShiftScaleMatrix = shiftScaleEnabled ? cellArrayBufferObject.getInverseShiftAndScaleMatrix() : null;
|
|
662
740
|
const mat = inverseShiftScaleMatrix ? mat4.copy(model.imagematinv, actor.getMatrix()) : actor.getMatrix();
|
|
663
741
|
if (inverseShiftScaleMatrix) {
|
|
664
742
|
mat4.transpose(mat, mat);
|
|
@@ -677,17 +755,17 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
|
|
|
677
755
|
planeEquations.push(planeEquation[j]);
|
|
678
756
|
}
|
|
679
757
|
}
|
|
680
|
-
|
|
681
|
-
|
|
758
|
+
program.setUniformi('numClipPlanes', numClipPlanes);
|
|
759
|
+
program.setUniform4fv('clipPlanes', planeEquations);
|
|
682
760
|
}
|
|
683
761
|
|
|
684
762
|
// handle coincident
|
|
685
|
-
if (
|
|
763
|
+
if (program.isUniformUsed('coffset')) {
|
|
686
764
|
const cp = publicAPI.getCoincidentParameters(ren, actor);
|
|
687
|
-
|
|
765
|
+
program.setUniformf('coffset', cp.offset);
|
|
688
766
|
// cfactor isn't always used when coffset is.
|
|
689
|
-
if (
|
|
690
|
-
|
|
767
|
+
if (program.isUniformUsed('cfactor')) {
|
|
768
|
+
program.setUniformf('cfactor', cp.factor);
|
|
691
769
|
}
|
|
692
770
|
}
|
|
693
771
|
};
|
|
@@ -184,10 +184,8 @@ function vtkOpenGLRenderWindow(publicAPI, model) {
|
|
|
184
184
|
return model.containerSize || model.size;
|
|
185
185
|
};
|
|
186
186
|
publicAPI.getFramebufferSize = () => {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
return model.size;
|
|
187
|
+
const fbSize = model.activeFramebuffer?.getSize();
|
|
188
|
+
return fbSize || model.size;
|
|
191
189
|
};
|
|
192
190
|
publicAPI.getPixelData = (x1, y1, x2, y2) => {
|
|
193
191
|
const pixels = new Uint8Array((x2 - x1 + 1) * (y2 - y1 + 1) * 4);
|
|
@@ -133,7 +133,8 @@ function vtkLineIntegralConvolution2D(publicAPI, model) {
|
|
|
133
133
|
}
|
|
134
134
|
const gl = model.context;
|
|
135
135
|
let fb = model.framebuffer;
|
|
136
|
-
|
|
136
|
+
const fbSize = fb.getSize();
|
|
137
|
+
if (!fb || !fbSize || size[0] !== fbSize || size[1] !== fbSize) {
|
|
137
138
|
fb = vtkOpenGLFramebuffer.newInstance();
|
|
138
139
|
fb.setOpenGLRenderWindow(model._openGLRenderWindow);
|
|
139
140
|
fb.saveCurrentBindingsAndBuffers();
|
|
@@ -813,7 +813,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
|
|
|
813
813
|
model.framebuffer.populateFramebuffer();
|
|
814
814
|
} else {
|
|
815
815
|
const fbSize = model.framebuffer.getSize();
|
|
816
|
-
if (fbSize[0] !== size[0] || fbSize[1] !== size[1]) {
|
|
816
|
+
if (!fbSize || fbSize[0] !== size[0] || fbSize[1] !== size[1]) {
|
|
817
817
|
model.framebuffer.create(size[0], size[1]);
|
|
818
818
|
model.framebuffer.populateFramebuffer();
|
|
819
819
|
}
|
package/index.d.ts
CHANGED
|
@@ -160,6 +160,7 @@
|
|
|
160
160
|
/// <reference path="./Rendering/Core/Glyph3DMapper.d.ts" />
|
|
161
161
|
/// <reference path="./Rendering/Core/HardwareSelector.d.ts" />
|
|
162
162
|
/// <reference path="./Rendering/Core/ImageArrayMapper.d.ts" />
|
|
163
|
+
/// <reference path="./Rendering/Core/ImageCPRMapper/Constants.d.ts" />
|
|
163
164
|
/// <reference path="./Rendering/Core/ImageCPRMapper.d.ts" />
|
|
164
165
|
/// <reference path="./Rendering/Core/ImageMapper/Constants.d.ts" />
|
|
165
166
|
/// <reference path="./Rendering/Core/ImageMapper.d.ts" />
|