vtk.js 29.7.3 → 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.
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @project vtk.js
3
- * @build Wed, Feb 28, 2024 4:00 PM ET
3
+ * @build Thu, Mar 7, 2024 1:05 PM ET
4
4
  * @copyright Copyright (c) 2024 Kitware, Inc.
5
5
  *
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @project vtk.js
3
- * @build Wed, Feb 28, 2024 4:00 PM ET
3
+ * @build Thu, Mar 7, 2024 1:05 PM ET
4
4
  * @copyright Copyright (c) 2024 Kitware, Inc.
5
5
  *
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @project vtk.js
3
- * @build Wed, Feb 28, 2024 4:00 PM ET
3
+ * @build Thu, Mar 7, 2024 1:05 PM ET
4
4
  * @copyright Copyright (c) 2024 Kitware, Inc.
5
5
  *
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @project vtk.js
3
- * @build Wed, Feb 28, 2024 4:00 PM ET
3
+ * @build Thu, Mar 7, 2024 1:05 PM ET
4
4
  * @copyright Copyright (c) 2024 Kitware, Inc.
5
5
  *
6
6
  */
@@ -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,9 @@
1
+ export const ProjectionMode = {
2
+ MAX: 0,
3
+ MIN: 1,
4
+ AVERAGE: 2,
5
+ };
6
+
7
+ export default {
8
+ ProjectionMode,
9
+ };
@@ -6,6 +6,7 @@ import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
6
6
  import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
7
7
  import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
8
8
  import vtkPolyLine from 'vtk.js/Sources/Common/DataModel/PolyLine';
9
+ import { ProjectionMode } from 'vtk.js/Sources/Rendering/Core/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 'vtk.js/Sources/Rendering/Core/AbstractImageM
4
4
  import macro from 'vtk.js/Sources/macros';
5
5
  import vtkPoints from 'vtk.js/Sources/Common/Core/Points';
6
6
  import vtkPolyLine from 'vtk.js/Sources/Common/DataModel/PolyLine';
7
+ import { ProjectionMode } from './Constants';
7
8
 
8
9
  const { vtkErrorMacro } = macro;
9
10
 
@@ -103,7 +104,10 @@ function vtkImageCPRMapper(publicAPI, model) {
103
104
  : orientationDataArray.getNumberOfComponents();
104
105
  switch (numComps) {
105
106
  case 16:
106
- convert = mat4.getRotation;
107
+ convert = (outQuat, inMat) => {
108
+ mat4.getRotation(outQuat, inMat);
109
+ quat.normalize(outQuat, outQuat);
110
+ };
107
111
  break;
108
112
  case 9:
109
113
  convert = (outQuat, inMat) => {
@@ -303,6 +307,8 @@ function vtkImageCPRMapper(publicAPI, model) {
303
307
  });
304
308
  };
305
309
 
310
+ publicAPI.isProjectionEnabled = () => model.projectionSlabNumberOfSamples > 1;
311
+
306
312
  publicAPI.setCenterlineData = (centerlineData) =>
307
313
  publicAPI.setInputData(centerlineData, 1);
308
314
 
@@ -335,6 +341,9 @@ const DEFAULT_VALUES = {
335
341
  tangentDirection: [1, 0, 0],
336
342
  bitangentDirection: [0, 1, 0],
337
343
  normalDirection: [0, 0, 1],
344
+ projectionSlabThickness: 1,
345
+ projectionSlabNumberOfSamples: 1,
346
+ projectionMode: ProjectionMode.MAX,
338
347
  };
339
348
 
340
349
  // ----------------------------------------------------------------------------
@@ -362,6 +371,9 @@ export function extend(publicAPI, model, initialValues = {}) {
362
371
  'tangentDirection',
363
372
  'bitangentDirection',
364
373
  'normalDirection',
374
+ 'projectionSlabThickness',
375
+ 'projectionSlabNumberOfSamples',
376
+ 'projectionMode',
365
377
  ]);
366
378
  CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);
367
379
 
@@ -1,15 +1,16 @@
1
1
  import macro from 'vtk.js/Sources/macros';
2
2
  import { mat4, vec3 } from 'gl-matrix';
3
- import vtkViewNode from 'vtk.js/Sources/Rendering/SceneGraph/ViewNode';
3
+ import { Filter } from 'vtk.js/Sources/Rendering/OpenGL/Texture/Constants';
4
+ import { InterpolationType } from 'vtk.js/Sources/Rendering/Core/ImageProperty/Constants';
5
+ import { ProjectionMode } from 'vtk.js/Sources/Rendering/Core/ImageCPRMapper/Constants';
6
+ import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants';
7
+ import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants';
8
+ import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
4
9
  import vtkHelper from 'vtk.js/Sources/Rendering/OpenGL/Helper';
10
+ import vtkOpenGLTexture from 'vtk.js/Sources/Rendering/OpenGL/Texture';
5
11
  import vtkReplacementShaderMapper from 'vtk.js/Sources/Rendering/OpenGL/ReplacementShaderMapper';
6
12
  import vtkShaderProgram from 'vtk.js/Sources/Rendering/OpenGL/ShaderProgram';
7
- import vtkOpenGLTexture from 'vtk.js/Sources/Rendering/OpenGL/Texture';
8
- import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
9
- import { VtkDataTypes } from 'vtk.js/Sources/Common/Core/DataArray/Constants';
10
- import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants';
11
- import { Filter } from 'vtk.js/Sources/Rendering/OpenGL/Texture/Constants';
12
- import { InterpolationType } from 'vtk.js/Sources/Rendering/Core/ImageProperty/Constants';
13
+ import vtkViewNode from 'vtk.js/Sources/Rendering/SceneGraph/ViewNode';
13
14
 
14
15
  import vtkPolyDataVS from 'vtk.js/Sources/Rendering/OpenGL/glsl/vtkPolyDataVS.glsl';
15
16
  import vtkPolyDataFS from 'vtk.js/Sources/Rendering/OpenGL/glsl/vtkPolyDataFS.glsl';
@@ -451,46 +452,39 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
451
452
  const customAttributes = [centerlinePosition, quadIndex];
452
453
 
453
454
  if (!model.renderable.getUseUniformOrientation()) {
454
- // For each {quad / centerline segment}, two vectors in directionDataArray give the orientation of the centerline
455
- // Send these two vectors to each vertex and use flat interpolation to get them as is in the fragment shader
456
- // The interpolation will occur in the fragment shader (slerp)
457
- const directions = model.renderable.getCenterlineTangentDirections();
458
- const centerlineTopDirectionArray = new Float32Array(3 * nPts);
459
- const centerlineBotDirectionArray = new Float32Array(3 * nPts);
460
- for (let lineIdx = 0, offset = 0; lineIdx < nLines; ++lineIdx) {
461
- const baseDirectionIdx = 3 * lineIdx;
462
-
463
- // Every vertex of each quad/segment have the same topDir and botDir
464
- // Top left, Top right, Bottom right, Bottom left
465
- for (let i = 0; i < 4; ++i) {
466
- // Top array
467
- centerlineTopDirectionArray[offset + 0] =
468
- directions[baseDirectionIdx + 0];
469
- centerlineTopDirectionArray[offset + 1] =
470
- directions[baseDirectionIdx + 1];
471
- centerlineTopDirectionArray[offset + 2] =
472
- directions[baseDirectionIdx + 2];
473
- // Bot array
474
- centerlineBotDirectionArray[offset + 0] =
475
- directions[baseDirectionIdx + 3];
476
- centerlineBotDirectionArray[offset + 1] =
477
- directions[baseDirectionIdx + 4];
478
- centerlineBotDirectionArray[offset + 2] =
479
- directions[baseDirectionIdx + 5];
480
- offset += 3;
455
+ // For each quad (i.e. centerline segment), a top and bottom quaternion give the orientation
456
+ // Send both quaternions to each vertex and use flat interpolation to get them "as is" in the fragment shader
457
+ // The interpolation of the quaternions will occur in the fragment shader (slerp)
458
+ const orientationQuats =
459
+ model.renderable.getOrientedCenterline().getOrientations() ?? [];
460
+ const centerlineTopOrientationArray = new Float32Array(4 * nPts);
461
+ const centerlineBotOrientationArray = new Float32Array(4 * nPts);
462
+ for (let quadIdx = 0; quadIdx < nLines; ++quadIdx) {
463
+ // All vertices of a given quad have the same topDir and botDir
464
+ // Polyline goes from top to bottom
465
+ const topQuat = orientationQuats[quadIdx];
466
+ const botQuat = orientationQuats[quadIdx + 1];
467
+ for (let pointInQuadIdx = 0; pointInQuadIdx < 4; ++pointInQuadIdx) {
468
+ const pointIdx = pointInQuadIdx + 4 * quadIdx;
469
+ const quaternionArrayOffset = 4 * pointIdx;
470
+ centerlineTopOrientationArray.set(topQuat, quaternionArrayOffset);
471
+ centerlineBotOrientationArray.set(botQuat, quaternionArrayOffset);
481
472
  }
482
473
  }
483
- const centerlineTopDirection = vtkDataArray.newInstance({
484
- numberOfComponents: 3,
485
- values: centerlineTopDirectionArray,
486
- name: 'centerlineTopDirection',
474
+ const centerlineTopOrientation = vtkDataArray.newInstance({
475
+ numberOfComponents: 4,
476
+ values: centerlineTopOrientationArray,
477
+ name: 'centerlineTopOrientation',
487
478
  });
488
- const centerlineBotDirection = vtkDataArray.newInstance({
489
- numberOfComponents: 3,
490
- values: centerlineBotDirectionArray,
491
- name: 'centerlineBotDirection',
479
+ const centerlineBotOrientation = vtkDataArray.newInstance({
480
+ numberOfComponents: 4,
481
+ values: centerlineBotOrientationArray,
482
+ name: 'centerlineBotOrientation',
492
483
  });
493
- customAttributes.push(centerlineTopDirection, centerlineBotDirection);
484
+ customAttributes.push(
485
+ centerlineTopOrientation,
486
+ centerlineBotOrientation
487
+ );
494
488
  }
495
489
 
496
490
  model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, {
@@ -515,17 +509,22 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
515
509
  const iComp = actor.getProperty().getIndependentComponents();
516
510
  const useCenterPoint = !!model.renderable.getCenterPoint();
517
511
  const useUniformOrientation = model.renderable.getUseUniformOrientation();
512
+ const projectionMode =
513
+ model.renderable.isProjectionEnabled() &&
514
+ model.renderable.getProjectionMode();
518
515
 
519
516
  if (
520
517
  cellBO.getProgram() === 0 ||
521
518
  model.lastUseCenterPoint !== useCenterPoint ||
522
519
  model.lastUseUniformOrientation !== useUniformOrientation ||
520
+ model.lastProjectionMode !== projectionMode ||
523
521
  model.lastHaveSeenDepthRequest !== model.haveSeenDepthRequest ||
524
522
  model.lastTextureComponents !== tNumComp ||
525
523
  model.lastIndependentComponents !== iComp
526
524
  ) {
527
525
  model.lastUseCenterPoint = useCenterPoint;
528
526
  model.lastUseUniformOrientation = useUniformOrientation;
527
+ model.lastProjectionMode = projectionMode;
529
528
  model.lastHaveSeenDepthRequest = model.haveSeenDepthRequest;
530
529
  model.lastTextureComponents = tNumComp;
531
530
  model.lastIndependentComponents = iComp;
@@ -544,6 +543,26 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
544
543
  let VSSource = shaders.Vertex;
545
544
  let FSSource = shaders.Fragment;
546
545
 
546
+ // https://glmatrix.net/docs/vec3.js.html#line522
547
+ const applyQuaternionToVecShaderFunction = [
548
+ 'vec3 applyQuaternionToVec(vec4 q, vec3 v) {',
549
+ ' float uvx = q.y * v.z - q.z * v.y;',
550
+ ' float uvy = q.z * v.x - q.x * v.z;',
551
+ ' float uvz = q.x * v.y - q.y * v.x;',
552
+ ' float uuvx = q.y * uvz - q.z * uvy;',
553
+ ' float uuvy = q.z * uvx - q.x * uvz;',
554
+ ' float uuvz = q.x * uvy - q.y * uvx;',
555
+ ' float w2 = q.w * 2.0;',
556
+ ' uvx *= w2;',
557
+ ' uvy *= w2;',
558
+ ' uvz *= w2;',
559
+ ' uuvx *= 2.0;',
560
+ ' uuvy *= 2.0;',
561
+ ' uuvz *= 2.0;',
562
+ ' return vec3(v.x + uvx + uuvx, v.y + uvy + uuvy, v.z + uvz + uuvz);',
563
+ '}',
564
+ ];
565
+
547
566
  // Vertex shader main replacements
548
567
  VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', [
549
568
  'uniform mat4 MCPCMatrix;',
@@ -561,19 +580,27 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
561
580
  'out vec2 quadOffsetVSOutput;',
562
581
  'out vec3 centerlinePosVSOutput;',
563
582
  ];
583
+ const useProjection = model.renderable.isProjectionEnabled();
564
584
  const isDirectionUniform = model.renderable.getUseUniformOrientation();
565
585
  if (isDirectionUniform) {
566
586
  vsColorDec.push(
567
- 'out vec3 centerlineDirVSOutput;',
568
- 'uniform vec3 centerlineDirection;'
587
+ 'out vec3 samplingDirVSOutput;',
588
+ 'uniform vec4 centerlineOrientation;',
589
+ 'uniform vec3 tangentDirection;',
590
+ ...applyQuaternionToVecShaderFunction
569
591
  );
592
+ if (useProjection) {
593
+ vsColorDec.push(
594
+ 'out vec3 projectionDirVSOutput;',
595
+ 'uniform vec3 bitangentDirection;'
596
+ );
597
+ }
570
598
  } else {
571
599
  vsColorDec.push(
572
- 'out vec3 centerlineTopDirVSOutput;',
573
- 'out vec3 centerlineBotDirVSOutput;',
574
- 'out float centerlineAngleVSOutput;',
575
- 'attribute vec3 centerlineTopDirection;',
576
- 'attribute vec3 centerlineBotDirection;'
600
+ 'out vec4 centerlineTopOrientationVSOutput;',
601
+ 'out vec4 centerlineBotOrientationVSOutput;',
602
+ 'attribute vec4 centerlineTopOrientation;',
603
+ 'attribute vec4 centerlineBotOrientation;'
577
604
  );
578
605
  }
579
606
  VSSource = vtkShaderProgram.substitute(
@@ -589,35 +616,18 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
589
616
  'centerlinePosVSOutput = centerlinePosition;',
590
617
  ];
591
618
  if (isDirectionUniform) {
592
- vsColorImpl.push('centerlineDirVSOutput = centerlineDirection;');
619
+ vsColorImpl.push(
620
+ 'samplingDirVSOutput = applyQuaternionToVec(centerlineOrientation, tangentDirection);'
621
+ );
622
+ if (useProjection) {
623
+ vsColorImpl.push(
624
+ 'projectionDirVSOutput = applyQuaternionToVec(centerlineOrientation, bitangentDirection);'
625
+ );
626
+ }
593
627
  } else {
594
628
  vsColorImpl.push(
595
- // When u and v are unit vectors: uvAngle = 2 * atan2(|| u - v ||, || u + v ||)
596
- // When u != -v: || u + v || > 0
597
- // When x > 0: atan2(y, x) = atan(y/x)
598
- // Thus: dirAngle = 2 * atan(|| topDir - botDir || / || topDir + botDir ||)
599
- // This is more stable and should not be to slow compared to acos(dot(u, v))
600
- 'vec3 sumVec = centerlineTopDirection + centerlineBotDirection;',
601
- 'float sumLen2 = dot(sumVec, sumVec);',
602
- 'float diffLen2 = 4.0 - sumLen2;',
603
- 'if (diffLen2 < 0.001) {',
604
- ' // vectors are too close to each other, use lerp',
605
- ' centerlineAngleVSOutput = -1.0; // use negative angle as a flag for lerp',
606
- ' centerlineTopDirVSOutput = centerlineTopDirection;',
607
- ' centerlineBotDirVSOutput = centerlineBotDirection;',
608
- '} else if (sumLen2 == 0.0) {',
609
- " // vector are opposite to each other, don't make a choice for the user",
610
- ' // use slerp without direction, it will display the centerline color on each row of pixel',
611
- ' centerlineAngleVSOutput = 0.0;',
612
- ' centerlineTopDirVSOutput = vec3(0.0);',
613
- ' centerlineBotDirVSOutput = vec3(0.0);',
614
- '} else {',
615
- ' // use slerp',
616
- ' centerlineAngleVSOutput = 2.0 * atan(sqrt(diffLen2/sumLen2));',
617
- ' float sinAngle = sin(centerlineAngleVSOutput);',
618
- ' centerlineTopDirVSOutput = centerlineTopDirection / sinAngle;',
619
- ' centerlineBotDirVSOutput = centerlineBotDirection / sinAngle;',
620
- '}'
629
+ 'centerlineTopOrientationVSOutput = centerlineTopOrientation;',
630
+ 'centerlineBotOrientationVSOutput = centerlineBotOrientation;'
621
631
  );
622
632
  }
623
633
  VSSource = vtkShaderProgram.substitute(
@@ -652,14 +662,29 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
652
662
  `uniform float pwfshift0;`,
653
663
  `uniform float pwfscale0;`,
654
664
  ];
665
+ if (useProjection) {
666
+ tcoordFSDec.push(
667
+ 'uniform vec3 spacing;',
668
+ 'uniform int projectionSlabNumberOfSamples;',
669
+ 'uniform float projectionConstantOffset;',
670
+ 'uniform float projectionStepLength;'
671
+ );
672
+ }
655
673
  if (isDirectionUniform) {
656
- tcoordFSDec.push('in vec3 centerlineDirVSOutput;');
674
+ tcoordFSDec.push('in vec3 samplingDirVSOutput;');
675
+ if (useProjection) {
676
+ tcoordFSDec.push('in vec3 projectionDirVSOutput;');
677
+ }
657
678
  } else {
658
679
  tcoordFSDec.push(
659
- 'in vec3 centerlineTopDirVSOutput;',
660
- 'in vec3 centerlineBotDirVSOutput;',
661
- 'in float centerlineAngleVSOutput;'
680
+ 'uniform vec3 tangentDirection;',
681
+ 'in vec4 centerlineTopOrientationVSOutput;',
682
+ 'in vec4 centerlineBotOrientationVSOutput;',
683
+ ...applyQuaternionToVecShaderFunction
662
684
  );
685
+ if (useProjection) {
686
+ tcoordFSDec.push('uniform vec3 bitangentDirection;');
687
+ }
663
688
  }
664
689
  const centerPoint = model.renderable.getCenterPoint();
665
690
  if (centerPoint) {
@@ -730,47 +755,103 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
730
755
 
731
756
  let tcoordFSImpl = [];
732
757
  if (isDirectionUniform) {
733
- tcoordFSImpl.push(
734
- 'vec3 interpolatedCenterlineDir = centerlineDirVSOutput;'
735
- );
758
+ tcoordFSImpl.push('vec3 samplingDirection = samplingDirVSOutput;');
759
+ if (useProjection) {
760
+ tcoordFSImpl.push('vec3 projectionDirection = projectionDirVSOutput;');
761
+ }
736
762
  } else {
737
763
  // Slerp or lerp between centerlineTopDirVSOutput and centerlineBotDirVSOutput
738
764
  // We use quadOffsetVSOutput.y: bottom = 0.0; top = 1.0;
739
765
  tcoordFSImpl.push(
740
- 'vec3 interpolatedCenterlineDir;',
741
- 'if (centerlineAngleVSOutput < 0.0) {',
742
- ' // Lerp',
743
- ' interpolatedCenterlineDir = quadOffsetVSOutput.y * centerlineTopDirVSOutput + (1.0 - quadOffsetVSOutput.y) * centerlineBotDirVSOutput;',
766
+ // Slerp / Lerp
767
+ 'vec4 q0 = centerlineBotOrientationVSOutput;',
768
+ 'vec4 q1 = centerlineTopOrientationVSOutput;',
769
+ 'float qCosAngle = dot(q0, q1);',
770
+ 'vec4 interpolatedOrientation;',
771
+ 'if (qCosAngle > 0.999 || qCosAngle < -0.999) {',
772
+ ' // Use LERP instead of SLERP when the two quaternions are close or opposite',
773
+ ' interpolatedOrientation = normalize(mix(q0, q1, quadOffsetVSOutput.y));',
744
774
  '} else {',
745
- ' // Slerp',
746
- ' float topInterpolationAngle = quadOffsetVSOutput.y * centerlineAngleVSOutput;',
747
- ' float botInterpolationAngle = centerlineAngleVSOutput - topInterpolationAngle;',
748
- ' interpolatedCenterlineDir = sin(topInterpolationAngle) * centerlineTopDirVSOutput + sin(botInterpolationAngle) * centerlineBotDirVSOutput;',
775
+ ' float omega = acos(qCosAngle);',
776
+ ' interpolatedOrientation = normalize(sin((1.0 - quadOffsetVSOutput.y) * omega) * q0 + sin(quadOffsetVSOutput.y * omega) * q1);',
749
777
  '}',
750
- '// Slerp should give a normalized vector but when sin(angle) is small, rounding error occurs',
751
- '// Normalize for both lerp and slerp',
752
- 'interpolatedCenterlineDir = normalize(interpolatedCenterlineDir);'
778
+ 'vec3 samplingDirection = applyQuaternionToVec(interpolatedOrientation, tangentDirection);'
753
779
  );
780
+ if (useProjection) {
781
+ tcoordFSImpl.push(
782
+ 'vec3 projectionDirection = applyQuaternionToVec(interpolatedOrientation, bitangentDirection);'
783
+ );
784
+ }
754
785
  }
755
786
  if (centerPoint) {
756
787
  tcoordFSImpl.push(
757
- 'float baseOffset = dot(interpolatedCenterlineDir, globalCenterPoint - centerlinePosVSOutput);',
788
+ 'float baseOffset = dot(samplingDirection, globalCenterPoint - centerlinePosVSOutput);',
758
789
  'float horizontalOffset = quadOffsetVSOutput.x + baseOffset;'
759
790
  );
760
791
  } else {
761
792
  tcoordFSImpl.push('float horizontalOffset = quadOffsetVSOutput.x;');
762
793
  }
763
794
  tcoordFSImpl.push(
764
- 'vec3 volumePosMC = centerlinePosVSOutput + horizontalOffset * interpolatedCenterlineDir;',
795
+ 'vec3 volumePosMC = centerlinePosVSOutput + horizontalOffset * samplingDirection;',
765
796
  'vec3 volumePosTC = (MCTCMatrix * vec4(volumePosMC, 1.0)).xyz;',
766
797
  'if (any(lessThan(volumePosTC, vec3(0.0))) || any(greaterThan(volumePosTC, vec3(1.0))))',
767
798
  '{',
768
799
  ' // set the background color and exit',
769
800
  ' gl_FragData[0] = backgroundColor;',
770
801
  ' return;',
771
- '}',
772
- 'vec4 tvalue = texture(volumeTexture, volumePosTC);'
802
+ '}'
773
803
  );
804
+
805
+ if (useProjection) {
806
+ const projectionMode = model.renderable.getProjectionMode();
807
+ switch (projectionMode) {
808
+ case ProjectionMode.MIN:
809
+ tcoordFSImpl.push(
810
+ 'const vec4 initialProjectionTextureValue = vec4(1.0);'
811
+ );
812
+ break;
813
+ case ProjectionMode.MAX:
814
+ case ProjectionMode.AVERAGE:
815
+ default:
816
+ tcoordFSImpl.push(
817
+ 'const vec4 initialProjectionTextureValue = vec4(0.0);'
818
+ );
819
+ break;
820
+ }
821
+
822
+ // Loop on all the samples of the projection
823
+ tcoordFSImpl.push(
824
+ 'vec3 projectionScaledDirection = projectionDirection / spacing;',
825
+ 'vec3 projectionStep = projectionStepLength * projectionScaledDirection;',
826
+ 'vec3 projectionStartPosition = volumePosTC + projectionConstantOffset * projectionScaledDirection;',
827
+ 'vec4 tvalue = initialProjectionTextureValue;',
828
+ 'for (int projectionSampleIdx = 0; projectionSampleIdx < projectionSlabNumberOfSamples; ++projectionSampleIdx) {',
829
+ ' vec3 projectionSamplePosition = projectionStartPosition + float(projectionSampleIdx) * projectionStep;',
830
+ ' vec4 sampledTextureValue = texture(volumeTexture, projectionSamplePosition);'
831
+ );
832
+ switch (projectionMode) {
833
+ case ProjectionMode.MAX:
834
+ tcoordFSImpl.push(' tvalue = max(tvalue, sampledTextureValue);');
835
+ break;
836
+ case ProjectionMode.MIN:
837
+ tcoordFSImpl.push(' tvalue = min(tvalue, sampledTextureValue);');
838
+ break;
839
+ case ProjectionMode.AVERAGE:
840
+ default:
841
+ tcoordFSImpl.push(' tvalue = tvalue + sampledTextureValue;');
842
+ break;
843
+ }
844
+ tcoordFSImpl.push('}');
845
+
846
+ // Process the total if needed
847
+ if (projectionMode === ProjectionMode.AVERAGE) {
848
+ tcoordFSImpl.push(
849
+ 'tvalue = tvalue / float(projectionSlabNumberOfSamples);'
850
+ );
851
+ }
852
+ } else {
853
+ tcoordFSImpl.push('vec4 tvalue = texture(volumeTexture, volumePosTC);');
854
+ }
774
855
  if (iComps) {
775
856
  const rgba = ['r', 'g', 'b', 'a'];
776
857
  for (let comp = 0; comp < tNumComp; ++comp) {
@@ -923,23 +1004,25 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
923
1004
  };
924
1005
 
925
1006
  publicAPI.setMapperShaderParameters = (cellBO, ren, actor) => {
1007
+ const program = cellBO.getProgram();
1008
+ const cellArrayBufferObject = cellBO.getCABO();
926
1009
  if (
927
- cellBO.getCABO().getElementCount() &&
1010
+ cellArrayBufferObject.getElementCount() &&
928
1011
  (model.VBOBuildTime.getMTime() >
929
1012
  cellBO.getAttributeUpdateTime().getMTime() ||
930
1013
  cellBO.getShaderSourceTime().getMTime() >
931
1014
  cellBO.getAttributeUpdateTime().getMTime())
932
1015
  ) {
933
- if (cellBO.getProgram().isAttributeUsed('vertexMC')) {
1016
+ if (program.isAttributeUsed('vertexMC')) {
934
1017
  if (
935
1018
  !cellBO
936
1019
  .getVAO()
937
1020
  .addAttributeArray(
938
- cellBO.getProgram(),
939
- cellBO.getCABO(),
1021
+ program,
1022
+ cellArrayBufferObject,
940
1023
  'vertexMC',
941
- cellBO.getCABO().getVertexOffset(),
942
- cellBO.getCABO().getStride(),
1024
+ cellArrayBufferObject.getVertexOffset(),
1025
+ cellArrayBufferObject.getStride(),
943
1026
  model.context.FLOAT,
944
1027
  3,
945
1028
  model.context.FALSE
@@ -956,15 +1039,15 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
956
1039
  .forEach((data) => {
957
1040
  if (
958
1041
  data &&
959
- cellBO.getProgram().isAttributeUsed(data.name) &&
1042
+ program.isAttributeUsed(data.name) &&
960
1043
  !cellBO
961
1044
  .getVAO()
962
1045
  .addAttributeArray(
963
- cellBO.getProgram(),
964
- cellBO.getCABO(),
1046
+ program,
1047
+ cellArrayBufferObject,
965
1048
  data.name,
966
1049
  data.offset,
967
- cellBO.getCABO().getStride(),
1050
+ cellArrayBufferObject.getStride(),
968
1051
  model.context.FLOAT,
969
1052
  data.components,
970
1053
  model.context.FALSE
@@ -977,24 +1060,53 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
977
1060
  }
978
1061
 
979
1062
  const texUnit = model.volumeTexture.getTextureUnit();
980
- cellBO.getProgram().setUniformi('volumeTexture', texUnit);
981
- cellBO.getProgram().setUniformf('width', model.renderable.getWidth());
1063
+ program.setUniformi('volumeTexture', texUnit);
1064
+ program.setUniformf('width', model.renderable.getWidth());
982
1065
  cellBO
983
1066
  .getProgram()
984
- .setUniform4f(
985
- 'backgroundColor',
986
- ...model.renderable.getBackgroundColor()
987
- );
1067
+ .setUniform4fv('backgroundColor', model.renderable.getBackgroundColor());
988
1068
 
989
- if (cellBO.getProgram().isUniformUsed('centerlineDirection')) {
990
- const uniformDirection = model.renderable.getUniformDirection();
1069
+ if (program.isUniformUsed('tangentDirection')) {
1070
+ const tangentDirection = model.renderable.getTangentDirection();
991
1071
  cellBO
992
1072
  .getProgram()
993
- .setUniform3fArray('centerlineDirection', uniformDirection);
1073
+ .setUniform3fArray('tangentDirection', tangentDirection);
994
1074
  }
995
- if (cellBO.getProgram().isUniformUsed('globalCenterPoint')) {
1075
+ if (program.isUniformUsed('bitangentDirection')) {
1076
+ const bitangentDirection = model.renderable.getBitangentDirection();
1077
+ cellBO
1078
+ .getProgram()
1079
+ .setUniform3fArray('bitangentDirection', bitangentDirection);
1080
+ }
1081
+ if (program.isUniformUsed('centerlineOrientation')) {
1082
+ const uniformOrientation = model.renderable.getUniformOrientation();
1083
+ cellBO
1084
+ .getProgram()
1085
+ .setUniform4fv('centerlineOrientation', uniformOrientation);
1086
+ }
1087
+ if (program.isUniformUsed('globalCenterPoint')) {
996
1088
  const centerPoint = model.renderable.getCenterPoint();
997
- cellBO.getProgram().setUniform3fArray('globalCenterPoint', centerPoint);
1089
+ program.setUniform3fArray('globalCenterPoint', centerPoint);
1090
+ }
1091
+ // Projection uniforms
1092
+ if (model.renderable.isProjectionEnabled()) {
1093
+ const image = model.currentImageDataInput;
1094
+ const spacing = image.getSpacing();
1095
+ const projectionSlabThickness =
1096
+ model.renderable.getProjectionSlabThickness();
1097
+ const projectionSlabNumberOfSamples =
1098
+ model.renderable.getProjectionSlabNumberOfSamples();
1099
+
1100
+ program.setUniform3fArray('spacing', spacing);
1101
+ program.setUniformi(
1102
+ 'projectionSlabNumberOfSamples',
1103
+ projectionSlabNumberOfSamples
1104
+ );
1105
+ const constantOffset = -0.5 * projectionSlabThickness;
1106
+ program.setUniformf('projectionConstantOffset', constantOffset);
1107
+ const stepLength =
1108
+ projectionSlabThickness / (projectionSlabNumberOfSamples - 1);
1109
+ program.setUniformf('projectionStepLength', stepLength);
998
1110
  }
999
1111
 
1000
1112
  // Model coordinates to image space
@@ -1008,7 +1120,7 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
1008
1120
  vec3.inverse([], image.getDimensions())
1009
1121
  );
1010
1122
  const MCTCMatrix = mat4.mul(ICTCMatrix, ICTCMatrix, MCICMatrix);
1011
- cellBO.getProgram().setUniformMatrix('MCTCMatrix', MCTCMatrix);
1123
+ program.setUniformMatrix('MCTCMatrix', MCTCMatrix);
1012
1124
 
1013
1125
  if (model.haveSeenDepthRequest) {
1014
1126
  cellBO
@@ -1024,9 +1136,10 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
1024
1136
  numClipPlanes = 6;
1025
1137
  }
1026
1138
 
1027
- const shiftScaleEnabled = cellBO.getCABO().getCoordShiftAndScaleEnabled();
1139
+ const shiftScaleEnabled =
1140
+ cellArrayBufferObject.getCoordShiftAndScaleEnabled();
1028
1141
  const inverseShiftScaleMatrix = shiftScaleEnabled
1029
- ? cellBO.getCABO().getInverseShiftAndScaleMatrix()
1142
+ ? cellArrayBufferObject.getInverseShiftAndScaleMatrix()
1030
1143
  : null;
1031
1144
  const mat = inverseShiftScaleMatrix
1032
1145
  ? mat4.copy(model.imagematinv, actor.getMatrix())
@@ -1057,17 +1170,17 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
1057
1170
  planeEquations.push(planeEquation[j]);
1058
1171
  }
1059
1172
  }
1060
- cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
1061
- cellBO.getProgram().setUniform4fv('clipPlanes', planeEquations);
1173
+ program.setUniformi('numClipPlanes', numClipPlanes);
1174
+ program.setUniform4fv('clipPlanes', planeEquations);
1062
1175
  }
1063
1176
 
1064
1177
  // handle coincident
1065
- if (cellBO.getProgram().isUniformUsed('coffset')) {
1178
+ if (program.isUniformUsed('coffset')) {
1066
1179
  const cp = publicAPI.getCoincidentParameters(ren, actor);
1067
- cellBO.getProgram().setUniformf('coffset', cp.offset);
1180
+ program.setUniformf('coffset', cp.offset);
1068
1181
  // cfactor isn't always used when coffset is.
1069
- if (cellBO.getProgram().isUniformUsed('cfactor')) {
1070
- cellBO.getProgram().setUniformf('cfactor', cp.factor);
1182
+ if (program.isUniformUsed('cfactor')) {
1183
+ program.setUniformf('cfactor', cp.factor);
1071
1184
  }
1072
1185
  }
1073
1186
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vtk.js",
3
- "version": "29.7.3",
3
+ "version": "29.8.0",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",
package/vtk-bundle.html CHANGED
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8"/>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
6
- <title>vtk.js [28 Feb 2024 at 16:01]</title>
6
+ <title>vtk.js [7 Mar 2024 at 13:06]</title>
7
7
  <link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABrVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+O1foceMD///+J0/qK1Pr7/v8Xdr/9///W8P4UdL7L7P0Scr2r4Pyj3vwad8D5/f/2/f+55f3E6f34+/2H0/ojfMKpzOd0rNgQcb3F3O/j9f7c8v6g3Pz0/P/w+v/q+P7n9v6T1/uQ1vuE0vqLut/y+v+Z2fvt+f+15Pzv9fuc2/vR7v2V2Pvd6/bg9P7I6/285/2y4/yp3/zp8vk8i8kqgMT7/P31+fyv4vxGkcz6/P6/6P3j7vfS5PNnpNUxhcbO7f7F6v3O4vHK3/DA2u631Ouy0eqXweKJud5wqthfoNMMbLvY8f73+v2dxeR8sNtTmdDx9/zX6PSjyeaCtd1YnNGX2PuQveCGt95Nls42h8dLlM3F4vBtAAAAM3RSTlMAAyOx0/sKBvik8opWGBMOAe3l1snDm2E9LSb06eHcu5JpHbarfHZCN9CBb08zzkdNS0kYaptYAAAFV0lEQVRYw92X51/aYBDHHS2O2qqttVbrqNq9m+TJIAYIShBkWwqIiCgoWvfeq7Z2/s29hyQNyUcR7LveGwVyXy6XH8/9rqxglLfUPLxVduUor3h0rfp2TYvpivk37929TkG037hffoX0+peVtZQc1589rigVUdXS/ABSAyEmGIO/1XfvldSK8vs3OqB6u3m0nxmIrvgB0dj7rr7Y9IbuF68hnfFaiHA/sxqm0wciIG43P60qKv9WXWc1RXGh/mFESFABTSBi0sNAKzqet17eCtOb3kZIDwxEEU0oAIJGYxNBDhBND29e0rtXXbcpuPmED9IhEAAQ/AXEaF8EPmnrrKsv0LvWR3fg5sWDNAFZOgAgaKvZDogHNU9MFwnnYROkc56RD5CjAbQX9Ow4g7upCsvYu55aSI/Nj0H1akgKQEUM94dwK65hYRmFU9MIcH/fqJYOZYcnuJSU/waKDgTOEVaVKhwrTRP5XzgSpAITYzom7UvkhFX5VutmxeNnWDjjswTKTyfgluNDGbUpWissXhF3s7mlSml+czWkg3D0l1nNjGNjz3myOQOa1KM/jOS6ebdbAVTCi4gljHSFrviza7tOgRWcS0MOUX9zdNgag5w7rRqA44Lzw0hr1WqES36dFliSJFlh2rXIae3FFcDDgKdxrUIDePr8jGcSClV1u7A9xeN0ModY/pHMxmR1EzRh8TJiwqsHmKW0l4FCEZI+jHio+JdPPE9qwQtTRxku2D8sIeRL2LnxWSllANCQGOIiqVHAz2ye2JR0DcH+HoxDkaADLjgxjKQ+AwCX/g0+DNgdG0ukYCONAe+dbc2IAc6fwt1ARoDSezNHxV2Cmzwv3O6lDMV55edBGwGK9n1+x2F8EDfAGCxug8MhpsMEcTEAWf3rx2vZhe/LAmtIn/6apE6PN0ULKgywD9mmdxbmFl3OvD5AS5fW5zLbv/YHmcsBTjf/afDz3MaZTVCfAP9z6/Bw6ycv8EUBWJIn9zYcoAWWlW9+OzO3vkTy8H+RANLmdrpOuYWdZYEXpo+TlCJrW5EARb7fF+bWdqf3hhyZI1nWJQHgznErZhbjoEsWqi8dQNoE294aldzFurwSABL2XXMf9+H1VQGke9exw5P/AnA5Pv5ngMul7LOvO922iwACu8WkCwLCafvM4CeWPxfA8lNHcWZSoi8EwMAIciKX2Z4SWCMAa3snCZ/G4EA8D6CMLNFsGQhkkz/gQNEBbPCbWsxGUpYVu3z8IyNAknwJkfPMEhLyrdi5RTyUVACkw4GSFRNWJNEW+fgPGwHD8/JxnRuLabN4CGNRkAE23na2+VmEAUmrYymSGjMAYqH84YUIyzgzs3XC7gNgH36Vcc4zKY9o9fgPBXUAiHHwVboBHGLiX6Zcjp1f2wu4tvzZKo0ecPnDtQYDQvJXaBeNzce45Fp28ZQLrEZVuFqgBwOalArKXnW1UzlnSusQKJqKYNuz4tOnI6sZG4zanpemv+7ySU2jbA9h6uhcgpfy6G2PahirDZ6zvq6zDduMVFTKvzw8wgyEdelwY9in3XkEPs3osJuwRQ4qTkfzifndg9Gfc4pdsu82+tTnHZTBa2EAMrqr2t43pguc8tNm7JQVQ2S0ukj2d22dhXYP0/veWtwKrCkNoNimAN5+Xr/oLrxswKbVJjteWrX7eR63o4j9q0GxnaBdWgGA5VStpanIjQmEhV0/nVt5VOFUvix6awJhPcAaTEShgrG+iGyvb5a0Ndb1YGHFPEwoqAinoaykaID1o1pdPNu7XsnCKQ3R+hwWIIhGvORcJUBYXe3Xa3vq/mF/N9V13ugufMkfXn+KHsRD0B8AAAAASUVORK5CYII=" type="image/x-icon" />
8
8
 
9
9
  <script>
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8"/>
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
6
- <title>vtk.js [28 Feb 2024 at 16:01]</title>
6
+ <title>vtk.js [7 Mar 2024 at 13:06]</title>
7
7
  <link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABrVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+O1foceMD///+J0/qK1Pr7/v8Xdr/9///W8P4UdL7L7P0Scr2r4Pyj3vwad8D5/f/2/f+55f3E6f34+/2H0/ojfMKpzOd0rNgQcb3F3O/j9f7c8v6g3Pz0/P/w+v/q+P7n9v6T1/uQ1vuE0vqLut/y+v+Z2fvt+f+15Pzv9fuc2/vR7v2V2Pvd6/bg9P7I6/285/2y4/yp3/zp8vk8i8kqgMT7/P31+fyv4vxGkcz6/P6/6P3j7vfS5PNnpNUxhcbO7f7F6v3O4vHK3/DA2u631Ouy0eqXweKJud5wqthfoNMMbLvY8f73+v2dxeR8sNtTmdDx9/zX6PSjyeaCtd1YnNGX2PuQveCGt95Nls42h8dLlM3F4vBtAAAAM3RSTlMAAyOx0/sKBvik8opWGBMOAe3l1snDm2E9LSb06eHcu5JpHbarfHZCN9CBb08zzkdNS0kYaptYAAAFV0lEQVRYw92X51/aYBDHHS2O2qqttVbrqNq9m+TJIAYIShBkWwqIiCgoWvfeq7Z2/s29hyQNyUcR7LveGwVyXy6XH8/9rqxglLfUPLxVduUor3h0rfp2TYvpivk37929TkG037hffoX0+peVtZQc1589rigVUdXS/ABSAyEmGIO/1XfvldSK8vs3OqB6u3m0nxmIrvgB0dj7rr7Y9IbuF68hnfFaiHA/sxqm0wciIG43P60qKv9WXWc1RXGh/mFESFABTSBi0sNAKzqet17eCtOb3kZIDwxEEU0oAIJGYxNBDhBND29e0rtXXbcpuPmED9IhEAAQ/AXEaF8EPmnrrKsv0LvWR3fg5sWDNAFZOgAgaKvZDogHNU9MFwnnYROkc56RD5CjAbQX9Ow4g7upCsvYu55aSI/Nj0H1akgKQEUM94dwK65hYRmFU9MIcH/fqJYOZYcnuJSU/waKDgTOEVaVKhwrTRP5XzgSpAITYzom7UvkhFX5VutmxeNnWDjjswTKTyfgluNDGbUpWissXhF3s7mlSml+czWkg3D0l1nNjGNjz3myOQOa1KM/jOS6ebdbAVTCi4gljHSFrviza7tOgRWcS0MOUX9zdNgag5w7rRqA44Lzw0hr1WqES36dFliSJFlh2rXIae3FFcDDgKdxrUIDePr8jGcSClV1u7A9xeN0ModY/pHMxmR1EzRh8TJiwqsHmKW0l4FCEZI+jHio+JdPPE9qwQtTRxku2D8sIeRL2LnxWSllANCQGOIiqVHAz2ye2JR0DcH+HoxDkaADLjgxjKQ+AwCX/g0+DNgdG0ukYCONAe+dbc2IAc6fwt1ARoDSezNHxV2Cmzwv3O6lDMV55edBGwGK9n1+x2F8EDfAGCxug8MhpsMEcTEAWf3rx2vZhe/LAmtIn/6apE6PN0ULKgywD9mmdxbmFl3OvD5AS5fW5zLbv/YHmcsBTjf/afDz3MaZTVCfAP9z6/Bw6ycv8EUBWJIn9zYcoAWWlW9+OzO3vkTy8H+RANLmdrpOuYWdZYEXpo+TlCJrW5EARb7fF+bWdqf3hhyZI1nWJQHgznErZhbjoEsWqi8dQNoE294aldzFurwSABL2XXMf9+H1VQGke9exw5P/AnA5Pv5ngMul7LOvO922iwACu8WkCwLCafvM4CeWPxfA8lNHcWZSoi8EwMAIciKX2Z4SWCMAa3snCZ/G4EA8D6CMLNFsGQhkkz/gQNEBbPCbWsxGUpYVu3z8IyNAknwJkfPMEhLyrdi5RTyUVACkw4GSFRNWJNEW+fgPGwHD8/JxnRuLabN4CGNRkAE23na2+VmEAUmrYymSGjMAYqH84YUIyzgzs3XC7gNgH36Vcc4zKY9o9fgPBXUAiHHwVboBHGLiX6Zcjp1f2wu4tvzZKo0ecPnDtQYDQvJXaBeNzce45Fp28ZQLrEZVuFqgBwOalArKXnW1UzlnSusQKJqKYNuz4tOnI6sZG4zanpemv+7ySU2jbA9h6uhcgpfy6G2PahirDZ6zvq6zDduMVFTKvzw8wgyEdelwY9in3XkEPs3osJuwRQ4qTkfzifndg9Gfc4pdsu82+tTnHZTBa2EAMrqr2t43pguc8tNm7JQVQ2S0ukj2d22dhXYP0/veWtwKrCkNoNimAN5+Xr/oLrxswKbVJjteWrX7eR63o4j9q0GxnaBdWgGA5VStpanIjQmEhV0/nVt5VOFUvix6awJhPcAaTEShgrG+iGyvb5a0Ndb1YGHFPEwoqAinoaykaID1o1pdPNu7XsnCKQ3R+hwWIIhGvORcJUBYXe3Xa3vq/mF/N9V13ugufMkfXn+KHsRD0B8AAAAASUVORK5CYII=" type="image/x-icon" />
8
8
 
9
9
  <script>
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @project vtk.js
3
- * @build Wed, Feb 28, 2024 4:00 PM ET
3
+ * @build Thu, Mar 7, 2024 1:05 PM ET
4
4
  * @copyright Copyright (c) 2024 Kitware, Inc.
5
5
  *
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @project vtk.js
3
- * @build Wed, Feb 28, 2024 4:00 PM ET
3
+ * @build Thu, Mar 7, 2024 1:05 PM ET
4
4
  * @copyright Copyright (c) 2024 Kitware, Inc.
5
5
  *
6
6
  */