@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.
@@ -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
- cachedArraysMetaData[arrayId].lastAccess = new Date();
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);
@@ -0,0 +1,10 @@
1
+ export declare enum ProjectionMode {
2
+ MAX = 0,
3
+ MIN = 1,
4
+ AVERAGE = 2,
5
+ }
6
+
7
+ declare const _default: {
8
+ ProjectionMode: typeof ProjectionMode;
9
+ };
10
+ export default _default;
@@ -0,0 +1,10 @@
1
+ const ProjectionMode = {
2
+ MAX: 0,
3
+ MIN: 1,
4
+ AVERAGE: 2
5
+ };
6
+ var Constants = {
7
+ ProjectionMode
8
+ };
9
+
10
+ export { ProjectionMode, Constants as default };
@@ -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 = mat4.getRotation;
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
- const size = [0, 0];
152
- if (model.glFramebuffer !== null) {
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 vtkViewNode from '../SceneGraph/ViewNode.js';
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 vtkOpenGLTexture from './Texture.js';
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 {quad / centerline segment}, two vectors in directionDataArray give the orientation of the centerline
368
- // Send these two vectors to each vertex and use flat interpolation to get them as is in the fragment shader
369
- // The interpolation will occur in the fragment shader (slerp)
370
- const directions = model.renderable.getCenterlineTangentDirections();
371
- const centerlineTopDirectionArray = new Float32Array(3 * nPts);
372
- const centerlineBotDirectionArray = new Float32Array(3 * nPts);
373
- for (let lineIdx = 0, offset = 0; lineIdx < nLines; ++lineIdx) {
374
- const baseDirectionIdx = 3 * lineIdx;
375
-
376
- // Every vertex of each quad/segment have the same topDir and botDir
377
- // Top left, Top right, Bottom right, Bottom left
378
- for (let i = 0; i < 4; ++i) {
379
- // Top array
380
- centerlineTopDirectionArray[offset + 0] = directions[baseDirectionIdx + 0];
381
- centerlineTopDirectionArray[offset + 1] = directions[baseDirectionIdx + 1];
382
- centerlineTopDirectionArray[offset + 2] = directions[baseDirectionIdx + 2];
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 centerlineTopDirection = vtkDataArray.newInstance({
391
- numberOfComponents: 3,
392
- values: centerlineTopDirectionArray,
393
- name: 'centerlineTopDirection'
386
+ const centerlineTopOrientation = vtkDataArray.newInstance({
387
+ numberOfComponents: 4,
388
+ values: centerlineTopOrientationArray,
389
+ name: 'centerlineTopOrientation'
394
390
  });
395
- const centerlineBotDirection = vtkDataArray.newInstance({
396
- numberOfComponents: 3,
397
- values: centerlineBotDirectionArray,
398
- name: 'centerlineBotDirection'
391
+ const centerlineBotOrientation = vtkDataArray.newInstance({
392
+ numberOfComponents: 4,
393
+ values: centerlineBotOrientationArray,
394
+ name: 'centerlineBotOrientation'
399
395
  });
400
- customAttributes.push(centerlineTopDirection, centerlineBotDirection);
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
- if (cellBO.getProgram() === 0 || model.lastUseCenterPoint !== useCenterPoint || model.lastUseUniformOrientation !== useUniformOrientation || model.lastHaveSeenDepthRequest !== model.haveSeenDepthRequest || model.lastTextureComponents !== tNumComp || model.lastIndependentComponents !== iComp) {
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 centerlineDirVSOutput;', 'uniform vec3 centerlineDirection;');
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 vec3 centerlineTopDirVSOutput;', 'out vec3 centerlineBotDirVSOutput;', 'out float centerlineAngleVSOutput;', 'attribute vec3 centerlineTopDirection;', 'attribute vec3 centerlineBotDirection;');
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('centerlineDirVSOutput = centerlineDirection;');
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 centerlineDirVSOutput;');
493
+ tcoordFSDec.push('in vec3 samplingDirVSOutput;');
494
+ if (useProjection) {
495
+ tcoordFSDec.push('in vec3 projectionDirVSOutput;');
496
+ }
489
497
  } else {
490
- tcoordFSDec.push('in vec3 centerlineTopDirVSOutput;', 'in vec3 centerlineBotDirVSOutput;', 'in float centerlineAngleVSOutput;');
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 interpolatedCenterlineDir = centerlineDirVSOutput;');
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('vec3 interpolatedCenterlineDir;', 'if (centerlineAngleVSOutput < 0.0) {', ' // Lerp', ' interpolatedCenterlineDir = quadOffsetVSOutput.y * centerlineTopDirVSOutput + (1.0 - quadOffsetVSOutput.y) * centerlineBotDirVSOutput;', '} else {', ' // Slerp', ' float topInterpolationAngle = quadOffsetVSOutput.y * centerlineAngleVSOutput;', ' float botInterpolationAngle = centerlineAngleVSOutput - topInterpolationAngle;', ' interpolatedCenterlineDir = sin(topInterpolationAngle) * centerlineTopDirVSOutput + sin(botInterpolationAngle) * centerlineBotDirVSOutput;', '}', '// Slerp should give a normalized vector but when sin(angle) is small, rounding error occurs', '// Normalize for both lerp and slerp', 'interpolatedCenterlineDir = normalize(interpolatedCenterlineDir);');
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(interpolatedCenterlineDir, globalCenterPoint - centerlinePosVSOutput);', 'float horizontalOffset = quadOffsetVSOutput.x + baseOffset;');
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 * interpolatedCenterlineDir;', '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;', '}', 'vec4 tvalue = texture(volumeTexture, volumePosTC);');
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
- if (cellBO.getCABO().getElementCount() && (model.VBOBuildTime.getMTime() > cellBO.getAttributeUpdateTime().getMTime() || cellBO.getShaderSourceTime().getMTime() > cellBO.getAttributeUpdateTime().getMTime())) {
614
- if (cellBO.getProgram().isAttributeUsed('vertexMC')) {
615
- if (!cellBO.getVAO().addAttributeArray(cellBO.getProgram(), cellBO.getCABO(), 'vertexMC', cellBO.getCABO().getVertexOffset(), cellBO.getCABO().getStride(), model.context.FLOAT, 3, model.context.FALSE)) {
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 && cellBO.getProgram().isAttributeUsed(data.name) && !cellBO.getVAO().addAttributeArray(cellBO.getProgram(), cellBO.getCABO(), data.name, data.offset, cellBO.getCABO().getStride(), model.context.FLOAT, data.components, model.context.FALSE)) {
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
- cellBO.getProgram().setUniformi('volumeTexture', texUnit);
630
- cellBO.getProgram().setUniformf('width', model.renderable.getWidth());
631
- cellBO.getProgram().setUniform4f('backgroundColor', ...model.renderable.getBackgroundColor());
632
- if (cellBO.getProgram().isUniformUsed('centerlineDirection')) {
633
- const uniformDirection = model.renderable.getUniformDirection();
634
- cellBO.getProgram().setUniform3fArray('centerlineDirection', uniformDirection);
635
- }
636
- if (cellBO.getProgram().isUniformUsed('globalCenterPoint')) {
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
- cellBO.getProgram().setUniform3fArray('globalCenterPoint', centerPoint);
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
- cellBO.getProgram().setUniformMatrix('MCTCMatrix', MCTCMatrix);
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 = cellBO.getCABO().getCoordShiftAndScaleEnabled();
661
- const inverseShiftScaleMatrix = shiftScaleEnabled ? cellBO.getCABO().getInverseShiftAndScaleMatrix() : null;
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
- cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
681
- cellBO.getProgram().setUniform4fv('clipPlanes', planeEquations);
758
+ program.setUniformi('numClipPlanes', numClipPlanes);
759
+ program.setUniform4fv('clipPlanes', planeEquations);
682
760
  }
683
761
 
684
762
  // handle coincident
685
- if (cellBO.getProgram().isUniformUsed('coffset')) {
763
+ if (program.isUniformUsed('coffset')) {
686
764
  const cp = publicAPI.getCoincidentParameters(ren, actor);
687
- cellBO.getProgram().setUniformf('coffset', cp.offset);
765
+ program.setUniformf('coffset', cp.offset);
688
766
  // cfactor isn't always used when coffset is.
689
- if (cellBO.getProgram().isUniformUsed('cfactor')) {
690
- cellBO.getProgram().setUniformf('cfactor', cp.factor);
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
- if (model.activeFramebuffer) {
188
- return model.activeFramebuffer.getSize();
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
- if (!fb || size[0] !== fb.getSize()[0] || size[1] !== fb.getSize()[1]) {
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" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "29.7.2",
3
+ "version": "29.8.0",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",