@kitware/vtk.js 22.0.2 → 22.1.3

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.
@@ -92,7 +92,7 @@ function convertItkToVtkImage(itkImage) {
92
92
  imageData.getPointData().setScalars(pointData); // Associate the point data that are 3D vectors / tensors
93
93
  // Refer to itk-js/src/PixelTypes.js for numerical values
94
94
 
95
- switch (ITKPixelTypes[itkImage.imageType.pixelType]) {
95
+ switch (isITKWasm ? ITKPixelTypes[itkImage.imageType.pixelType] : itkImage.imageType.pixelType) {
96
96
  case ITKPixelTypes.Scalar:
97
97
  break;
98
98
 
@@ -10,15 +10,16 @@ export interface IStyle {
10
10
  fontSizeScale?: (res: number) => number;
11
11
  edgeThickness?: number;
12
12
  edgeColor?: string;
13
+ resolution?: number;
13
14
  }
14
15
 
15
- export interface IFaceProperty {
16
+ export interface IFaceProperty extends IStyle {
16
17
  text?: string;
17
18
  faceRotation?: number;
18
19
  }
19
20
 
20
21
  /**
21
- *
22
+ *
22
23
  */
23
24
  export interface IAnnotatedCubeActorInitialValues extends IActorInitialValues {
24
25
  }
@@ -18,7 +18,7 @@ var vtkWarningMacro = macro.vtkWarningMacro,
18
18
  var deviceInputMap = {
19
19
  'xr-standard': [Input.Trigger, Input.Grip, Input.TrackPad, Input.Thumbstick, Input.A, Input.B]
20
20
  };
21
- var handledEvents = ['StartAnimation', 'Animation', 'EndAnimation', 'MouseEnter', 'MouseLeave', 'StartMouseMove', 'MouseMove', 'EndMouseMove', 'LeftButtonPress', 'LeftButtonRelease', 'MiddleButtonPress', 'MiddleButtonRelease', 'RightButtonPress', 'RightButtonRelease', 'KeyPress', 'KeyDown', 'KeyUp', 'StartMouseWheel', 'MouseWheel', 'EndMouseWheel', 'StartPinch', 'Pinch', 'EndPinch', 'StartPan', 'Pan', 'EndPan', 'StartRotate', 'Rotate', 'EndRotate', 'Button3D', 'Move3D', 'StartPointerLock', 'EndPointerLock', 'StartInteraction', 'Interaction', 'EndInteraction'];
21
+ var handledEvents = ['StartAnimation', 'Animation', 'EndAnimation', 'MouseEnter', 'MouseLeave', 'StartMouseMove', 'MouseMove', 'EndMouseMove', 'LeftButtonPress', 'LeftButtonRelease', 'MiddleButtonPress', 'MiddleButtonRelease', 'RightButtonPress', 'RightButtonRelease', 'KeyPress', 'KeyDown', 'KeyUp', 'StartMouseWheel', 'MouseWheel', 'EndMouseWheel', 'StartPinch', 'Pinch', 'EndPinch', 'StartPan', 'Pan', 'EndPan', 'StartRotate', 'Rotate', 'EndRotate', 'Button3D', 'Move3D', 'StartPointerLock', 'EndPointerLock', 'StartInteraction', 'Interaction', 'EndInteraction', 'AnimationFrameRateUpdate'];
22
22
 
23
23
  function preventDefault(event) {
24
24
  if (event.cancelable) {
@@ -326,6 +326,8 @@ function vtkRenderWindowInteractor(publicAPI, model) {
326
326
  animationRequesters.add(requestor);
327
327
 
328
328
  if (animationRequesters.size === 1 && !model.xrAnimation) {
329
+ model._animationStartTime = Date.now();
330
+ model._animationFrameCount = 0;
329
331
  model.lastFrameTime = 0.1;
330
332
  model.lastFrameStart = Date.now();
331
333
  model.animationRequest = requestAnimationFrame(publicAPI.handleAnimation);
@@ -450,6 +452,14 @@ function vtkRenderWindowInteractor(publicAPI, model) {
450
452
 
451
453
  publicAPI.handleAnimation = function () {
452
454
  var currTime = Date.now();
455
+ model._animationFrameCount++;
456
+
457
+ if (currTime - model._animationStartTime > 1000.0 && model._animationFrameCount > 1) {
458
+ model.recentAnimationFrameRate = 1000.0 * (model._animationFrameCount - 1) / (currTime - model._animationStartTime);
459
+ publicAPI.animationFrameRateUpdateEvent();
460
+ model._animationStartTime = currTime;
461
+ model._animationFrameCount = 1;
462
+ }
453
463
 
454
464
  if (model.FrameTime === -1.0) {
455
465
  model.lastFrameTime = 0.1;
@@ -498,7 +508,7 @@ function vtkRenderWindowInteractor(publicAPI, model) {
498
508
  model.wheelTimeoutID = setTimeout(function () {
499
509
  publicAPI.endMouseWheelEvent();
500
510
  model.wheelTimeoutID = 0;
501
- }, 200);
511
+ }, 800);
502
512
  };
503
513
 
504
514
  publicAPI.handleMouseEnter = function (event) {
@@ -924,6 +934,8 @@ function vtkRenderWindowInteractor(publicAPI, model) {
924
934
 
925
935
  publicAPI.handleVisibilityChange = function () {
926
936
  model.lastFrameStart = Date.now();
937
+ model._animationStartTime = Date.now();
938
+ model._animationFrameCount = 0;
927
939
  };
928
940
 
929
941
  publicAPI.setCurrentRenderer = function (r) {
@@ -975,6 +987,7 @@ var DEFAULT_VALUES = {
975
987
  currentGesture: 'Start',
976
988
  animationRequest: null,
977
989
  lastFrameTime: 0.1,
990
+ recentAnimationFrameRate: 10.0,
978
991
  wheelTimeoutID: 0,
979
992
  moveTimeoutID: 0,
980
993
  lastGamepadValues: {}
@@ -990,7 +1003,7 @@ function extend(publicAPI, model) {
990
1003
  return macro.event(publicAPI, model, eventName);
991
1004
  }); // Create get-only macros
992
1005
 
993
- macro.get(publicAPI, model, ['initialized', 'container', 'interactorStyle', 'lastFrameTime', 'view']); // Create get-set macros
1006
+ macro.get(publicAPI, model, ['initialized', 'container', 'interactorStyle', 'lastFrameTime', 'recentAnimationFrameRate', 'view']); // Create get-set macros
994
1007
 
995
1008
  macro.setGet(publicAPI, model, ['lightFollowCamera', 'enabled', 'enableRender', 'recognizeGestures', 'desiredUpdateRate', 'stillUpdateRate', 'picker']); // For more macro methods, see "Sources/macros.js"
996
1009
  // Object specific methods
@@ -8,6 +8,8 @@ import vtkWebGPUMapperHelper from './MapperHelper.js';
8
8
  import vtkWebGPURenderEncoder from './RenderEncoder.js';
9
9
  import vtkWebGPUShaderCache from './ShaderCache.js';
10
10
  import vtkWebGPUTexture from './Texture.js';
11
+ import vtkWebGPUUniformBuffer from './UniformBuffer.js';
12
+ import vtkWebGPUFullScreenQuad from './FullScreenQuad.js';
11
13
  import vtkWebGPUVolumePassFSQ from './VolumePassFSQ.js';
12
14
  import { f as distance2BetweenPoints } from '../../Common/Core/Math/index.js';
13
15
 
@@ -32,6 +34,7 @@ var BufferUsage = vtkWebGPUBufferManager.BufferUsage,
32
34
 
33
35
  var cubeFaceTriangles = [[0, 4, 6], [0, 6, 2], [1, 3, 7], [1, 7, 5], [0, 5, 4], [0, 1, 5], [2, 6, 7], [2, 7, 3], [0, 3, 1], [0, 2, 3], [4, 5, 7], [4, 7, 6]];
34
36
  var DepthBoundsFS = "\n//VTK::Renderer::Dec\n\n//VTK::Select::Dec\n\n//VTK::VolumePass::Dec\n\n//VTK::TCoord::Dec\n\n//VTK::RenderEncoder::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::IOStructs::Dec\n\n[[stage(fragment)]]\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output : fragmentOutput;\n\n //VTK::Select::Impl\n\n //VTK::TCoord::Impl\n\n //VTK::VolumePass::Impl\n\n // use the maximum (closest) of the current value and the zbuffer\n // the blend func will then take the min to find the farthest stop value\n var stopval: f32 = max(input.fragPos.z, textureLoad(opaquePassDepthTexture, vec2<i32>(i32(input.fragPos.x), i32(input.fragPos.y)), 0));\n\n //VTK::RenderEncoder::Impl\n return output;\n}\n";
37
+ var volumeCopyFragTemplate = "\n//VTK::Renderer::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::TCoord::Dec\n\n//VTK::RenderEncoder::Dec\n\n//VTK::IOStructs::Dec\n\n[[stage(fragment)]]\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output: fragmentOutput;\n\n var computedColor: vec4<f32> = textureSample(volumePassSmallColorTexture,\n volumePassSmallColorTextureSampler, mapperUBO.tscale*input.tcoordVS);\n\n //VTK::RenderEncoder::Impl\n return output;\n}\n";
35
38
  /* eslint-disable no-undef */
36
39
 
37
40
  /* eslint-disable no-bitwise */
@@ -39,7 +42,51 @@ var DepthBoundsFS = "\n//VTK::Renderer::Dec\n\n//VTK::Select::Dec\n\n//VTK::Volu
39
42
 
40
43
  function vtkWebGPUVolumePass(publicAPI, model) {
41
44
  // Set our className
42
- model.classHierarchy.push('vtkWebGPUVolumePass');
45
+ model.classHierarchy.push('vtkWebGPUVolumePass'); // create the required textures, encoders, FSQ etc
46
+
47
+ publicAPI.initialize = function (viewNode) {
48
+ if (!model._clearEncoder) {
49
+ publicAPI.createClearEncoder(viewNode);
50
+ }
51
+
52
+ if (!model._mergeEncoder) {
53
+ publicAPI.createMergeEncoder(viewNode);
54
+ }
55
+
56
+ if (!model._copyEncoder) {
57
+ publicAPI.createCopyEncoder(viewNode);
58
+ }
59
+
60
+ if (!model._depthRangeEncoder) {
61
+ publicAPI.createDepthRangeEncoder(viewNode);
62
+ }
63
+
64
+ if (!model.fullScreenQuad) {
65
+ model.fullScreenQuad = vtkWebGPUVolumePassFSQ.newInstance();
66
+ model.fullScreenQuad.setDevice(viewNode.getDevice());
67
+ model.fullScreenQuad.setTextureViews(_toConsumableArray(model._depthRangeEncoder.getColorTextureViews()));
68
+ }
69
+
70
+ if (!model._volumeCopyQuadQuad) {
71
+ model._volumeCopyQuad = vtkWebGPUFullScreenQuad.newInstance();
72
+
73
+ model._volumeCopyQuad.setPipelineHash('volpassfsq');
74
+
75
+ model._volumeCopyQuad.setDevice(viewNode.getDevice());
76
+
77
+ model._volumeCopyQuad.setFragmentShaderTemplate(volumeCopyFragTemplate);
78
+
79
+ model._copyUBO = vtkWebGPUUniformBuffer.newInstance();
80
+
81
+ model._copyUBO.setName('mapperUBO');
82
+
83
+ model._copyUBO.addEntry('tscale', 'vec2<f32>');
84
+
85
+ model._volumeCopyQuad.setUBO(model._copyUBO);
86
+
87
+ model._volumeCopyQuad.setTextureViews([model._colorTextureView]);
88
+ }
89
+ };
43
90
 
44
91
  publicAPI.traverse = function (renNode, viewNode) {
45
92
  if (model.deleted) {
@@ -47,29 +94,25 @@ function vtkWebGPUVolumePass(publicAPI, model) {
47
94
  } // we just render our delegates in order
48
95
 
49
96
 
50
- model.currentParent = viewNode; // then perform the ray casting using the depth bounds texture
97
+ model.currentParent = viewNode; // create stuff we need
51
98
 
52
- if (!model.finalEncoder) {
53
- publicAPI.createFinalEncoder(viewNode);
54
- } // first render the boxes to generate a min max depth
99
+ publicAPI.initialize(viewNode); // determine if we are rendering a small size
100
+
101
+ publicAPI.computeTiming(viewNode); // first render the boxes to generate a min max depth
55
102
  // map for all the volumes
56
103
 
104
+ publicAPI.renderDepthBounds(renNode, viewNode); // always mark true
57
105
 
58
- publicAPI.renderDepthBounds(renNode, viewNode);
106
+ model._firstGroup = true;
107
+ var device = viewNode.getDevice(); // determine how many volumes we can render at a time. We subtract
108
+ // 4 because we use know we use textures for min, max, ofun and tfun
59
109
 
60
- if (!model.fullScreenQuad) {
61
- model.fullScreenQuad = vtkWebGPUVolumePassFSQ.newInstance();
62
- model.fullScreenQuad.setDevice(viewNode.getDevice());
63
- model.fullScreenQuad.setTextureViews(_toConsumableArray(model.depthRangeEncoder.getColorTextureViews()));
64
- }
65
-
66
- var device = viewNode.getDevice(); // -4 because we use know we use textures for min, max, ofun and tfun
67
-
68
- var maxVolumes = device.getHandle().limits.maxSampledTexturesPerShaderStage - 4;
69
- var cameraPos = renNode.getRenderable().getActiveCamera().getPosition();
110
+ var maxVolumes = device.getHandle().limits.maxSampledTexturesPerShaderStage - 4; // if we have to make multiple passes then break the volumes up into groups
111
+ // rendered from farthest to closest
70
112
 
71
113
  if (model.volumes.length > maxVolumes) {
72
- // sort from back to front based on volume centroid
114
+ var cameraPos = renNode.getRenderable().getActiveCamera().getPosition(); // sort from back to front based on volume centroid
115
+
73
116
  var distances = [];
74
117
 
75
118
  for (var v = 0; v < model.volumes.length; v++) {
@@ -94,26 +137,117 @@ function vtkWebGPUVolumePass(publicAPI, model) {
94
137
  volumesToRender.push(model.volumes[volumeOrder[_v]]);
95
138
 
96
139
  if (volumesToRender.length >= chunkSize) {
97
- publicAPI.finalPass(viewNode, renNode, volumesToRender);
140
+ publicAPI.rayCastPass(viewNode, renNode, volumesToRender);
98
141
  volumesToRender = [];
99
142
  chunkSize = maxVolumes;
143
+ model._firstGroup = false;
100
144
  }
101
145
  }
102
146
  } else {
103
- publicAPI.finalPass(viewNode, renNode, model.volumes);
147
+ // if not rendering in chunks then just draw all of them at once
148
+ publicAPI.rayCastPass(viewNode, renNode, model.volumes);
149
+ } // copy back to the original color buffer
150
+ // final composite
151
+
152
+
153
+ model._copyEncoder.setColorTextureView(0, model.colorTextureView);
154
+
155
+ model._copyEncoder.attachTextureViews();
156
+
157
+ renNode.setRenderEncoder(model._copyEncoder);
158
+
159
+ model._copyEncoder.begin(viewNode.getCommandEncoder());
160
+
161
+ renNode.scissorAndViewport(model._copyEncoder);
162
+
163
+ model._volumeCopyQuad.setWebGPURenderer(renNode);
164
+
165
+ if (model._useSmallViewport) {
166
+ var width = model._colorTextureView.getTexture().getWidth();
167
+
168
+ var height = model._colorTextureView.getTexture().getHeight();
169
+
170
+ model._copyUBO.setArray('tscale', [model._smallViewportWidth / width, model._smallViewportHeight / height]);
171
+ } else {
172
+ model._copyUBO.setArray('tscale', [1.0, 1.0]);
173
+ }
174
+
175
+ model._copyUBO.sendIfNeeded(device);
176
+
177
+ model._volumeCopyQuad.render(model._copyEncoder, viewNode.getDevice());
178
+
179
+ model._copyEncoder.end();
180
+ }; // unsubscribe from our listeners
181
+
182
+
183
+ publicAPI.delete = macro.chain(function () {
184
+ if (model._animationRateSubscription) {
185
+ model._animationRateSubscription.unsubscribe();
186
+
187
+ model._animationRateSubscription = null;
188
+ }
189
+ }, publicAPI.delete);
190
+
191
+ publicAPI.computeTiming = function (viewNode) {
192
+ model._useSmallViewport = false;
193
+ var rwi = viewNode.getRenderable().getInteractor();
194
+
195
+ if (rwi.isAnimating() && model._lastScale > 1.5) {
196
+ if (!model._smallViewportHeight) {
197
+ model._smallViewportWidth = Math.ceil(viewNode.getCanvas().width / Math.sqrt(model._lastScale));
198
+ model._smallViewportHeight = Math.ceil(viewNode.getCanvas().height / Math.sqrt(model._lastScale));
199
+ }
200
+
201
+ model._useSmallViewport = true;
202
+ }
203
+
204
+ model._colorTexture.resize(viewNode.getCanvas().width, viewNode.getCanvas().height);
205
+
206
+ if (!model._animationRateSubscription) {
207
+ // when the animation frame rate changes recompute the scale factor
208
+ model._animationRateSubscription = rwi.onAnimationFrameRateUpdate(function () {
209
+ var frate = rwi.getRecentAnimationFrameRate();
210
+ var targetScale = model._lastScale * rwi.getDesiredUpdateRate() / frate;
211
+ model._lastScale = targetScale; // clamp scale to some reasonable values.
212
+ // Below 1.5 we will just be using full resolution as that is close enough
213
+ // Above 400 seems like a lot so we limit to that 1/20th per axis
214
+
215
+ if (model._lastScale > 400) {
216
+ model._lastScale = 400;
217
+ }
218
+
219
+ if (model._lastScale < 1.5) {
220
+ model._lastScale = 1.5;
221
+ } else {
222
+ model._smallViewportWidth = Math.ceil(viewNode.getCanvas().width / Math.sqrt(model._lastScale));
223
+ model._smallViewportHeight = Math.ceil(viewNode.getCanvas().height / Math.sqrt(model._lastScale));
224
+ }
225
+ });
104
226
  }
105
227
  };
106
228
 
107
- publicAPI.finalPass = function (viewNode, renNode, volumes) {
108
- model.finalEncoder.setColorTextureView(0, model.colorTextureView);
109
- model.finalEncoder.attachTextureViews();
110
- renNode.setRenderEncoder(model.finalEncoder);
111
- model.finalEncoder.begin(viewNode.getCommandEncoder());
112
- renNode.scissorAndViewport(model.finalEncoder);
229
+ publicAPI.rayCastPass = function (viewNode, renNode, volumes) {
230
+ var encoder = model._firstGroup ? model._clearEncoder : model._mergeEncoder;
231
+ encoder.attachTextureViews();
232
+ renNode.setRenderEncoder(encoder);
233
+ encoder.begin(viewNode.getCommandEncoder());
234
+
235
+ var width = model._colorTextureView.getTexture().getWidth();
236
+
237
+ var height = model._colorTextureView.getTexture().getHeight();
238
+
239
+ if (model._useSmallViewport) {
240
+ width = model._smallViewportWidth;
241
+ height = model._smallViewportHeight;
242
+ }
243
+
244
+ encoder.getHandle().setViewport(0, 0, width, height, 0.0, 1.0); // set scissor
245
+
246
+ encoder.getHandle().setScissorRect(0, 0, width, height);
113
247
  model.fullScreenQuad.setWebGPURenderer(renNode);
114
248
  model.fullScreenQuad.setVolumes(volumes);
115
- model.fullScreenQuad.render(model.finalEncoder, viewNode.getDevice());
116
- model.finalEncoder.end();
249
+ model.fullScreenQuad.render(encoder, viewNode.getDevice());
250
+ encoder.end();
117
251
  };
118
252
 
119
253
  publicAPI.renderDepthBounds = function (renNode, viewNode) {
@@ -211,55 +345,32 @@ function vtkWebGPUVolumePass(publicAPI, model) {
211
345
  publicAPI.drawDepthRange = function (renNode, viewNode) {
212
346
  var device = viewNode.getDevice(); // copy current depth buffer to
213
347
 
214
- if (!model.depthRangeEncoder) {
215
- publicAPI.createDepthRangeEncoder();
216
- model.depthRangeTexture = vtkWebGPUTexture.newInstance();
217
- model.depthRangeTexture.create(device, {
218
- width: viewNode.getCanvas().width,
219
- height: viewNode.getCanvas().height,
220
- format: 'r16float',
221
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
222
- });
223
- var maxView = model.depthRangeTexture.createView();
224
- maxView.setName('maxTexture');
225
- model.depthRangeEncoder.setColorTextureView(0, maxView);
226
- model.depthRangeTexture2 = vtkWebGPUTexture.newInstance();
227
- model.depthRangeTexture2.create(device, {
228
- width: viewNode.getCanvas().width,
229
- height: viewNode.getCanvas().height,
230
- format: 'r16float',
231
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
232
- });
233
- var minView = model.depthRangeTexture2.createView();
234
- minView.setName('minTexture');
235
- model.depthRangeEncoder.setColorTextureView(1, minView);
348
+ model._depthRangeTexture.resizeToMatch(model.colorTextureView.getTexture());
236
349
 
237
- model._mapper.setDevice(viewNode.getDevice());
350
+ model._depthRangeTexture2.resizeToMatch(model.colorTextureView.getTexture());
238
351
 
239
- model._mapper.setTextureViews([model.depthTextureView]);
240
- } else {
241
- model.depthRangeTexture.resizeToMatch(model.colorTextureView.getTexture());
242
- model.depthRangeTexture2.resizeToMatch(model.colorTextureView.getTexture());
243
- }
352
+ model._depthRangeEncoder.attachTextureViews();
244
353
 
245
- model.depthRangeEncoder.attachTextureViews();
246
354
  publicAPI.setCurrentOperation('volumeDepthRangePass');
247
- renNode.setRenderEncoder(model.depthRangeEncoder);
355
+ renNode.setRenderEncoder(model._depthRangeEncoder);
248
356
  renNode.volumeDepthRangePass(true);
249
357
 
250
358
  model._mapper.setWebGPURenderer(renNode);
251
359
 
252
- model._mapper.build(model.depthRangeEncoder, device);
360
+ model._mapper.build(model._depthRangeEncoder, device);
253
361
 
254
362
  model._mapper.registerToDraw();
255
363
 
256
364
  renNode.volumeDepthRangePass(false);
257
365
  };
258
366
 
259
- publicAPI.createDepthRangeEncoder = function () {
260
- model.depthRangeEncoder = vtkWebGPURenderEncoder.newInstance();
261
- model.depthRangeEncoder.setPipelineHash('volr');
262
- model.depthRangeEncoder.setReplaceShaderCodeFunction(function (pipeline) {
367
+ publicAPI.createDepthRangeEncoder = function (viewNode) {
368
+ var device = viewNode.getDevice();
369
+ model._depthRangeEncoder = vtkWebGPURenderEncoder.newInstance();
370
+
371
+ model._depthRangeEncoder.setPipelineHash('volr');
372
+
373
+ model._depthRangeEncoder.setReplaceShaderCodeFunction(function (pipeline) {
263
374
  var fDesc = pipeline.getShaderDescription('fragment');
264
375
  fDesc.addOutput('vec4<f32>', 'outColor1');
265
376
  fDesc.addOutput('vec4<f32>', 'outColor2');
@@ -267,7 +378,8 @@ function vtkWebGPUVolumePass(publicAPI, model) {
267
378
  code = vtkWebGPUShaderCache.substitute(code, '//VTK::RenderEncoder::Impl', ['output.outColor1 = vec4<f32>(input.fragPos.z, 0.0, 0.0, 0.0);', 'output.outColor2 = vec4<f32>(stopval, 0.0, 0.0, 0.0);']).result;
268
379
  fDesc.setCode(code);
269
380
  });
270
- model.depthRangeEncoder.setDescription({
381
+
382
+ model._depthRangeEncoder.setDescription({
271
383
  colorAttachments: [{
272
384
  view: null,
273
385
  loadValue: [0.0, 0.0, 0.0, 0.0],
@@ -278,7 +390,8 @@ function vtkWebGPUVolumePass(publicAPI, model) {
278
390
  storeOp: 'store'
279
391
  }]
280
392
  });
281
- model.depthRangeEncoder.setPipelineSettings({
393
+
394
+ model._depthRangeEncoder.setPipelineSettings({
282
395
  primitive: {
283
396
  cullMode: 'none'
284
397
  },
@@ -313,27 +426,162 @@ function vtkWebGPUVolumePass(publicAPI, model) {
313
426
  }
314
427
  }]
315
428
  }
429
+ }); // and the textures it needs
430
+
431
+
432
+ model._depthRangeTexture = vtkWebGPUTexture.newInstance();
433
+
434
+ model._depthRangeTexture.create(device, {
435
+ width: viewNode.getCanvas().width,
436
+ height: viewNode.getCanvas().height,
437
+ format: 'r16float',
438
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
439
+ });
440
+
441
+ var maxView = model._depthRangeTexture.createView();
442
+
443
+ maxView.setName('maxTexture');
444
+
445
+ model._depthRangeEncoder.setColorTextureView(0, maxView);
446
+
447
+ model._depthRangeTexture2 = vtkWebGPUTexture.newInstance();
448
+
449
+ model._depthRangeTexture2.create(device, {
450
+ width: viewNode.getCanvas().width,
451
+ height: viewNode.getCanvas().height,
452
+ format: 'r16float',
453
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
454
+ });
455
+
456
+ var minView = model._depthRangeTexture2.createView();
457
+
458
+ minView.setName('minTexture');
459
+
460
+ model._depthRangeEncoder.setColorTextureView(1, minView);
461
+
462
+ model._mapper.setDevice(viewNode.getDevice());
463
+
464
+ model._mapper.setTextureViews([model.depthTextureView]);
465
+ };
466
+
467
+ publicAPI.createClearEncoder = function (viewNode) {
468
+ model._colorTexture = vtkWebGPUTexture.newInstance();
469
+
470
+ model._colorTexture.create(viewNode.getDevice(), {
471
+ width: viewNode.getCanvas().width,
472
+ height: viewNode.getCanvas().height,
473
+ format: 'bgra8unorm',
474
+
475
+ /* eslint-disable no-undef */
476
+
477
+ /* eslint-disable no-bitwise */
478
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
479
+ });
480
+
481
+ model._colorTextureView = model._colorTexture.createView();
482
+
483
+ model._colorTextureView.setName('volumePassSmallColorTexture');
484
+
485
+ model._colorTextureView.addSampler(viewNode.getDevice(), {
486
+ minFilter: 'linear',
487
+ magFilter: 'linear'
488
+ });
489
+
490
+ model._clearEncoder = vtkWebGPURenderEncoder.newInstance();
491
+
492
+ model._clearEncoder.setColorTextureView(0, model._colorTextureView);
493
+
494
+ model._clearEncoder.setDescription({
495
+ colorAttachments: [{
496
+ view: null,
497
+ loadValue: [0.0, 0.0, 0.0, 0.0],
498
+ storeOp: 'store'
499
+ }]
500
+ });
501
+
502
+ model._clearEncoder.setPipelineHash('volpf');
503
+
504
+ model._clearEncoder.setPipelineSettings({
505
+ primitive: {
506
+ cullMode: 'none'
507
+ },
508
+ fragment: {
509
+ targets: [{
510
+ format: 'bgra8unorm',
511
+ blend: {
512
+ color: {
513
+ srcFactor: 'src-alpha',
514
+ dstFactor: 'one-minus-src-alpha'
515
+ },
516
+ alpha: {
517
+ srcfactor: 'one',
518
+ dstFactor: 'one-minus-src-alpha'
519
+ }
520
+ }
521
+ }]
522
+ }
316
523
  });
317
524
  };
318
525
 
319
- publicAPI.createFinalEncoder = function (viewNode) {
320
- model.finalEncoder = vtkWebGPURenderEncoder.newInstance();
321
- model.finalEncoder.setDescription({
526
+ publicAPI.createCopyEncoder = function (viewNode) {
527
+ model._copyEncoder = vtkWebGPURenderEncoder.newInstance();
528
+
529
+ model._copyEncoder.setDescription({
322
530
  colorAttachments: [{
323
531
  view: null,
324
532
  loadValue: 'load',
325
533
  storeOp: 'store'
326
534
  }]
327
535
  });
328
- model.finalEncoder.setReplaceShaderCodeFunction(function (pipeline) {
536
+
537
+ model._copyEncoder.setPipelineHash('volcopypf');
538
+
539
+ model._copyEncoder.setPipelineSettings({
540
+ primitive: {
541
+ cullMode: 'none'
542
+ },
543
+ fragment: {
544
+ targets: [{
545
+ format: 'bgra8unorm',
546
+ blend: {
547
+ color: {
548
+ srcFactor: 'one',
549
+ dstFactor: 'one-minus-src-alpha'
550
+ },
551
+ alpha: {
552
+ srcfactor: 'one',
553
+ dstFactor: 'one-minus-src-alpha'
554
+ }
555
+ }
556
+ }]
557
+ }
558
+ });
559
+ };
560
+
561
+ publicAPI.createMergeEncoder = function (viewNode) {
562
+ model._mergeEncoder = vtkWebGPURenderEncoder.newInstance();
563
+
564
+ model._mergeEncoder.setColorTextureView(0, model._colorTextureView);
565
+
566
+ model._mergeEncoder.setDescription({
567
+ colorAttachments: [{
568
+ view: null,
569
+ loadValue: 'load',
570
+ storeOp: 'store'
571
+ }]
572
+ });
573
+
574
+ model._mergeEncoder.setReplaceShaderCodeFunction(function (pipeline) {
329
575
  var fDesc = pipeline.getShaderDescription('fragment');
330
576
  fDesc.addOutput('vec4<f32>', 'outColor');
331
577
  var code = fDesc.getCode();
332
- code = vtkWebGPUShaderCache.substitute(code, '//VTK::RenderEncoder::Impl', ['output.outColor = vec4<f32>(computedColor.rgb*computedColor.a, computedColor.a);']).result;
578
+ code = vtkWebGPUShaderCache.substitute(code, '//VTK::RenderEncoder::Impl', ['output.outColor = vec4<f32>(computedColor.rgb, computedColor.a);']).result;
333
579
  fDesc.setCode(code);
334
580
  });
335
- model.finalEncoder.setPipelineHash('volpf');
336
- model.finalEncoder.setPipelineSettings({
581
+
582
+ model._mergeEncoder.setPipelineHash('volpf');
583
+
584
+ model._mergeEncoder.setPipelineSettings({
337
585
  primitive: {
338
586
  cullMode: 'none'
339
587
  },
@@ -342,7 +590,7 @@ function vtkWebGPUVolumePass(publicAPI, model) {
342
590
  format: 'bgra8unorm',
343
591
  blend: {
344
592
  color: {
345
- srcFactor: 'one',
593
+ srcFactor: 'src-alpha',
346
594
  dstFactor: 'one-minus-src-alpha'
347
595
  },
348
596
  alpha: {
@@ -387,6 +635,7 @@ function extend(publicAPI, model) {
387
635
  Object.assign(model, DEFAULT_VALUES, initialValues); // Build VTK API
388
636
 
389
637
  vtkRenderPass.extend(publicAPI, model, initialValues);
638
+ model._lastScale = 2.0;
390
639
  model._mapper = vtkWebGPUMapperHelper.newInstance();
391
640
 
392
641
  model._mapper.setFragmentShaderTemplate(DepthBoundsFS);
@@ -9,7 +9,7 @@ import vtkWebGPUSampler from './Sampler.js';
9
9
  import vtkWebGPUTypes from './Types.js';
10
10
  import { BlendMode } from '../Core/VolumeMapper/Constants.js';
11
11
 
12
- var volFragTemplate = "\n//VTK::Renderer::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::TCoord::Dec\n\n//VTK::Volume::TraverseDec\n\n//VTK::RenderEncoder::Dec\n\n//VTK::IOStructs::Dec\n\nfn getTextureValue(vTex: texture_3d<f32>, tpos: vec4<f32>) -> f32\n{\n // todo multicomponent support\n return textureSampleLevel(vTex, clampSampler, tpos.xyz, 0.0).r;\n}\n\nfn getGradient(vTex: texture_3d<f32>, tpos: vec4<f32>, vNum: i32, scalar: f32) -> vec4<f32>\n{\n var result: vec4<f32>;\n\n var tstep: vec4<f32> = volumeSSBO.values[vNum].tstep;\n result.x = getTextureValue(vTex, tpos + vec4<f32>(tstep.x, 0.0, 0.0, 1.0)) - scalar;\n result.y = getTextureValue(vTex, tpos + vec4<f32>(0.0, tstep.y, 0.0, 1.0)) - scalar;\n result.z = getTextureValue(vTex, tpos + vec4<f32>(0.0, 0.0, tstep.z, 1.0)) - scalar;\n\n // divide by spacing\n result = result / volumeSSBO.values[vNum].spacing;\n\n var grad: f32 = length(result.xyz);\n\n // // rotate to View Coords, needed for lighting and shading\n // result.xyz =\n // result.x * vPlaneNormal0 +\n // result.y * vPlaneNormal2 +\n // result.z * vPlaneNormal4;\n\n if (grad > 0.0)\n {\n result = result * (1.0 / grad);\n }\n\n result.w = grad;\n\n return result;\n}\n\nfn processVolume(vTex: texture_3d<f32>, vNum: i32, cNum: i32, posSC: vec4<f32>, tfunRows: f32) -> vec4<f32>\n{\n var outColor: vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n\n // convert to tcoords and reject if outside the volume\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*posSC;\n if (tpos.x < 0.0 || tpos.y < 0.0 || tpos.z < 0.0 ||\n tpos.x > 1.0 || tpos.y > 1.0 || tpos.z > 1.0) { return outColor; }\n\n var scalar: f32 = getTextureValue(vTex, tpos);\n\n var coord: vec2<f32> =\n vec2<f32>(scalar * componentSSBO.values[cNum].cScale + componentSSBO.values[cNum].cShift,\n (0.5 + 2.0 * f32(vNum)) / tfunRows);\n var color: vec4<f32> = textureSampleLevel(tfunTexture, clampSampler, coord, 0.0);\n\n var gofactor: f32 = 1.0;\n if (componentSSBO.values[cNum].gomin < 1.0)\n {\n var normal: vec4<f32> = getGradient(vTex, tpos, vNum, scalar);\n gofactor = clamp(normal.a*componentSSBO.values[cNum].goScale + componentSSBO.values[cNum].goShift,\n componentSSBO.values[cNum].gomin, componentSSBO.values[cNum].gomax);\n }\n\n coord.x = (scalar * componentSSBO.values[cNum].oScale + componentSSBO.values[cNum].oShift);\n var opacity: f32 = textureSampleLevel(ofunTexture, clampSampler, coord, 0.0).r;\n\n outColor = vec4<f32>(color.rgb, gofactor * opacity);\n\n//VTK::Volume::Process\n\n return outColor;\n}\n\n// adjust the start and end point of a raycast such that it intersects the unit cube.\n// This function is used to take a raycast starting point and step vector\n// and numSteps and return the startijng and ending steps for intersecting the\n// unit cube. Recall for a 3D texture, the unit cube is the range of texture coordsinates\n// that have valid values. So this funtion can be used to take a ray in texture coordinates\n// and bound it to intersecting the texture.\n//\nfn adjustBounds(tpos: vec4<f32>, tstep: vec4<f32>, numSteps: f32) -> vec2<f32>\n{\n var result: vec2<f32> = vec2<f32>(0.0, numSteps);\n var tpos2: vec4<f32> = tpos + tstep*numSteps;\n\n // move tpos to the start of the volume\n var adjust: f32 =\n min(\n max(tpos.x/tstep.x, (tpos.x - 1.0)/tstep.x),\n min(\n max((tpos.y - 1.0)/tstep.y, tpos.y/tstep.y),\n max((tpos.z - 1.0)/tstep.z, tpos.z/tstep.z)));\n if (adjust < 0.0)\n {\n result.x = result.x - adjust;\n }\n\n // adjust length to the end\n adjust =\n max(\n min(tpos2.x/tstep.x, (tpos2.x - 1.0)/tstep.x),\n max(\n min((tpos2.y - 1.0)/tstep.y, tpos2.y/tstep.y),\n min((tpos2.z - 1.0)/tstep.z, tpos2.z/tstep.z)));\n if (adjust > 0.0)\n {\n result.y = result.y - adjust;\n }\n\n return result;\n}\n\nfn getSimpleColor(scalar: f32, vNum: i32, cNum: i32) -> vec4<f32>\n{\n // how many rows (tfuns) do we have in our tfunTexture\n var tfunRows: f32 = f32(textureDimensions(tfunTexture).y);\n\n var coord: vec2<f32> =\n vec2<f32>(scalar * componentSSBO.values[cNum].cScale + componentSSBO.values[cNum].cShift,\n (0.5 + 2.0 * f32(vNum)) / tfunRows);\n var color: vec4<f32> = textureSampleLevel(tfunTexture, clampSampler, coord, 0.0);\n coord.x = (scalar * componentSSBO.values[cNum].oScale + componentSSBO.values[cNum].oShift);\n var opacity: f32 = textureSampleLevel(ofunTexture, clampSampler, coord, 0.0).r;\n return vec4<f32>(color.rgb, opacity);\n}\n\nfn traverseMax(vTex: texture_3d<f32>, vNum: i32, cNum: i32, rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>)\n{\n // convert to tcoords and reject if outside the volume\n var numSteps: f32 = rayLengthSC/mapperUBO.SampleDistance;\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*minPosSC;\n var tpos2: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*(minPosSC + rayStepSC);\n var tstep: vec4<f32> = tpos2 - tpos;\n\n var rayBounds: vec2<f32> = adjustBounds(tpos, tstep, numSteps);\n\n // did we hit anything\n if (rayBounds.x >= rayBounds.y)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n return;\n }\n\n tpos = tpos + tstep*rayBounds.x;\n var curDist: f32 = rayBounds.x;\n var maxVal: f32 = -1.0e37;\n loop\n {\n var scalar: f32 = getTextureValue(vTex, tpos);\n if (scalar > maxVal)\n {\n maxVal = scalar;\n }\n\n // increment position\n curDist = curDist + 1.0;\n tpos = tpos + tstep;\n\n // check if we have reached a terminating condition\n if (curDist > rayBounds.y) { break; }\n }\n\n // process to get the color and opacity\n traverseVals[vNum] = getSimpleColor(maxVal, vNum, cNum);\n}\n\nfn traverseMin(vTex: texture_3d<f32>, vNum: i32, cNum: i32, rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>)\n{\n // convert to tcoords and reject if outside the volume\n var numSteps: f32 = rayLengthSC/mapperUBO.SampleDistance;\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*minPosSC;\n var tpos2: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*(minPosSC + rayStepSC);\n var tstep: vec4<f32> = tpos2 - tpos;\n\n var rayBounds: vec2<f32> = adjustBounds(tpos, tstep, numSteps);\n\n // did we hit anything\n if (rayBounds.x >= rayBounds.y)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n return;\n }\n\n tpos = tpos + tstep*rayBounds.x;\n var curDist: f32 = rayBounds.x;\n var minVal: f32 = 1.0e37;\n loop\n {\n var scalar: f32 = getTextureValue(vTex, tpos);\n if (scalar < minVal)\n {\n minVal = scalar;\n }\n\n // increment position\n curDist = curDist + 1.0;\n tpos = tpos + tstep;\n\n // check if we have reached a terminating condition\n if (curDist > rayBounds.y) { break; }\n }\n\n // process to get the color and opacity\n traverseVals[vNum] = getSimpleColor(minVal, vNum, cNum);\n}\n\nfn traverseAverage(vTex: texture_3d<f32>, vNum: i32, cNum: i32, rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>)\n{\n // convert to tcoords and reject if outside the volume\n var numSteps: f32 = rayLengthSC/mapperUBO.SampleDistance;\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*minPosSC;\n var tpos2: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*(minPosSC + rayStepSC);\n var tstep: vec4<f32> = tpos2 - tpos;\n\n var rayBounds: vec2<f32> = adjustBounds(tpos, tstep, numSteps);\n\n // did we hit anything\n if (rayBounds.x >= rayBounds.y)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n return;\n }\n\n let ipRange: vec4<f32> = volumeSSBO.values[vNum].ipScalarRange;\n tpos = tpos + tstep*rayBounds.x;\n var curDist: f32 = rayBounds.x;\n var avgVal: f32 = 0.0;\n var sampleCount: f32 = 0.0;\n loop\n {\n var sample: f32 = getTextureValue(vTex, tpos);\n // right now leave filtering off until WebGL changes get merged\n // if (ipRange.z == 0.0 || sample >= ipRange.x && sample <= ipRange.y)\n // {\n avgVal = avgVal + sample;\n sampleCount = sampleCount + 1.0;\n // }\n\n // increment position\n curDist = curDist + 1.0;\n tpos = tpos + tstep;\n\n // check if we have reached a terminating condition\n if (curDist > rayBounds.y) { break; }\n }\n\n if (sampleCount <= 0.0)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n }\n\n // process to get the color and opacity\n traverseVals[vNum] = getSimpleColor(avgVal/sampleCount, vNum, cNum);\n}\n\nfn traverseAdditive(vTex: texture_3d<f32>, vNum: i32, cNum: i32, rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>)\n{\n // convert to tcoords and reject if outside the volume\n var numSteps: f32 = rayLengthSC/mapperUBO.SampleDistance;\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*minPosSC;\n var tpos2: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*(minPosSC + rayStepSC);\n var tstep: vec4<f32> = tpos2 - tpos;\n\n var rayBounds: vec2<f32> = adjustBounds(tpos, tstep, numSteps);\n\n // did we hit anything\n if (rayBounds.x >= rayBounds.y)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n return;\n }\n\n let ipRange: vec4<f32> = volumeSSBO.values[vNum].ipScalarRange;\n tpos = tpos + tstep*rayBounds.x;\n var curDist: f32 = rayBounds.x;\n var sumVal: f32 = 0.0;\n loop\n {\n var sample: f32 = getTextureValue(vTex, tpos);\n // right now leave filtering off until WebGL changes get merged\n // if (ipRange.z == 0.0 || sample >= ipRange.x && sample <= ipRange.y)\n // {\n sumVal = sumVal + sample;\n // }\n\n // increment position\n curDist = curDist + 1.0;\n tpos = tpos + tstep;\n\n // check if we have reached a terminating condition\n if (curDist > rayBounds.y) { break; }\n }\n\n // process to get the color and opacity\n traverseVals[vNum] = getSimpleColor(sumVal, vNum, cNum);\n}\n\nfn composite(rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>) -> vec4<f32>\n{\n // initial ray position is at the beginning\n var rayPosSC: vec4<f32> = minPosSC;\n\n // how many rows (tfuns) do we have in our tfunTexture\n var tfunRows: f32 = f32(textureDimensions(tfunTexture).y);\n\n var curDist: f32 = 0.0;\n var computedColor: vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n var sampleColor: vec4<f32>;\n//VTK::Volume::TraverseCalls\n\n loop\n {\n // for each volume, sample and accumulate color\n//VTK::Volume::CompositeCalls\n\n // increment position\n curDist = curDist + mapperUBO.SampleDistance;\n rayPosSC = rayPosSC + rayStepSC;\n\n // check if we have reached a terminating condition\n if (curDist > rayLengthSC) { break; }\n if (computedColor.a > 0.98) { break; }\n }\n return computedColor;\n}\n\n[[stage(fragment)]]\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output: fragmentOutput;\n\n var rayMax: f32 = textureSampleLevel(maxTexture, clampSampler, input.tcoordVS, 0.0).r;\n var rayMin: f32 = textureSampleLevel(minTexture, clampSampler, input.tcoordVS, 0.0).r;\n\n // discard empty rays\n if (rayMax <= rayMin) { discard; }\n else\n {\n var winDimsI32: vec2<i32> = textureDimensions(minTexture);\n var winDims: vec2<f32> = vec2<f32>(f32(winDimsI32.x), f32(winDimsI32.y));\n\n // compute start and end ray positions in view coordinates\n var minPosSC: vec4<f32> = rendererUBO.PCSCMatrix*vec4<f32>(2.0*input.fragPos.x/winDims.x - 1.0, 1.0 - 2.0 * input.fragPos.y/winDims.y, rayMax, 1.0);\n minPosSC = minPosSC * (1.0 / minPosSC.w);\n var maxPosSC: vec4<f32> = rendererUBO.PCSCMatrix*vec4<f32>(2.0*input.fragPos.x/winDims.x - 1.0, 1.0 - 2.0 * input.fragPos.y/winDims.y, rayMin, 1.0);\n maxPosSC = maxPosSC * (1.0 / maxPosSC.w);\n\n var rayLengthSC: f32 = distance(minPosSC.xyz, maxPosSC.xyz);\n var rayStepSC: vec4<f32> = (maxPosSC - minPosSC)*(mapperUBO.SampleDistance/rayLengthSC);\n rayStepSC.w = 0.0;\n\n var computedColor: vec4<f32>;\n\n//VTK::Volume::Loop\n\n//VTK::RenderEncoder::Impl\n }\n\n return output;\n}\n";
12
+ var volFragTemplate = "\n//VTK::Renderer::Dec\n\n//VTK::Mapper::Dec\n\n//VTK::TCoord::Dec\n\n//VTK::Volume::TraverseDec\n\n//VTK::RenderEncoder::Dec\n\n//VTK::IOStructs::Dec\n\nfn getTextureValue(vTex: texture_3d<f32>, tpos: vec4<f32>) -> f32\n{\n // todo multicomponent support\n return textureSampleLevel(vTex, clampSampler, tpos.xyz, 0.0).r;\n}\n\nfn getGradient(vTex: texture_3d<f32>, tpos: vec4<f32>, vNum: i32, scalar: f32) -> vec4<f32>\n{\n var result: vec4<f32>;\n\n var tstep: vec4<f32> = volumeSSBO.values[vNum].tstep;\n result.x = getTextureValue(vTex, tpos + vec4<f32>(tstep.x, 0.0, 0.0, 1.0)) - scalar;\n result.y = getTextureValue(vTex, tpos + vec4<f32>(0.0, tstep.y, 0.0, 1.0)) - scalar;\n result.z = getTextureValue(vTex, tpos + vec4<f32>(0.0, 0.0, tstep.z, 1.0)) - scalar;\n\n // divide by spacing\n result = result / volumeSSBO.values[vNum].spacing;\n\n var grad: f32 = length(result.xyz);\n\n // // rotate to View Coords, needed for lighting and shading\n // result.xyz =\n // result.x * vPlaneNormal0 +\n // result.y * vPlaneNormal2 +\n // result.z * vPlaneNormal4;\n\n if (grad > 0.0)\n {\n result = result * (1.0 / grad);\n }\n\n result.w = grad;\n\n return result;\n}\n\nfn processVolume(vTex: texture_3d<f32>, vNum: i32, cNum: i32, posSC: vec4<f32>, tfunRows: f32) -> vec4<f32>\n{\n var outColor: vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n\n // convert to tcoords and reject if outside the volume\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*posSC;\n if (tpos.x < 0.0 || tpos.y < 0.0 || tpos.z < 0.0 ||\n tpos.x > 1.0 || tpos.y > 1.0 || tpos.z > 1.0) { return outColor; }\n\n var scalar: f32 = getTextureValue(vTex, tpos);\n\n var coord: vec2<f32> =\n vec2<f32>(scalar * componentSSBO.values[cNum].cScale + componentSSBO.values[cNum].cShift,\n (0.5 + 2.0 * f32(vNum)) / tfunRows);\n var color: vec4<f32> = textureSampleLevel(tfunTexture, clampSampler, coord, 0.0);\n\n var gofactor: f32 = 1.0;\n if (componentSSBO.values[cNum].gomin < 1.0)\n {\n var normal: vec4<f32> = getGradient(vTex, tpos, vNum, scalar);\n gofactor = clamp(normal.a*componentSSBO.values[cNum].goScale + componentSSBO.values[cNum].goShift,\n componentSSBO.values[cNum].gomin, componentSSBO.values[cNum].gomax);\n }\n\n coord.x = (scalar * componentSSBO.values[cNum].oScale + componentSSBO.values[cNum].oShift);\n var opacity: f32 = textureSampleLevel(ofunTexture, clampSampler, coord, 0.0).r;\n\n outColor = vec4<f32>(color.rgb, gofactor * opacity);\n\n//VTK::Volume::Process\n\n return outColor;\n}\n\n// adjust the start and end point of a raycast such that it intersects the unit cube.\n// This function is used to take a raycast starting point and step vector\n// and numSteps and return the startijng and ending steps for intersecting the\n// unit cube. Recall for a 3D texture, the unit cube is the range of texture coordsinates\n// that have valid values. So this funtion can be used to take a ray in texture coordinates\n// and bound it to intersecting the texture.\n//\nfn adjustBounds(tpos: vec4<f32>, tstep: vec4<f32>, numSteps: f32) -> vec2<f32>\n{\n var result: vec2<f32> = vec2<f32>(0.0, numSteps);\n var tpos2: vec4<f32> = tpos + tstep*numSteps;\n\n // move tpos to the start of the volume\n var adjust: f32 =\n min(\n max(tpos.x/tstep.x, (tpos.x - 1.0)/tstep.x),\n min(\n max((tpos.y - 1.0)/tstep.y, tpos.y/tstep.y),\n max((tpos.z - 1.0)/tstep.z, tpos.z/tstep.z)));\n if (adjust < 0.0)\n {\n result.x = result.x - adjust;\n }\n\n // adjust length to the end\n adjust =\n max(\n min(tpos2.x/tstep.x, (tpos2.x - 1.0)/tstep.x),\n max(\n min((tpos2.y - 1.0)/tstep.y, tpos2.y/tstep.y),\n min((tpos2.z - 1.0)/tstep.z, tpos2.z/tstep.z)));\n if (adjust > 0.0)\n {\n result.y = result.y - adjust;\n }\n\n return result;\n}\n\nfn getSimpleColor(scalar: f32, vNum: i32, cNum: i32) -> vec4<f32>\n{\n // how many rows (tfuns) do we have in our tfunTexture\n var tfunRows: f32 = f32(textureDimensions(tfunTexture).y);\n\n var coord: vec2<f32> =\n vec2<f32>(scalar * componentSSBO.values[cNum].cScale + componentSSBO.values[cNum].cShift,\n (0.5 + 2.0 * f32(vNum)) / tfunRows);\n var color: vec4<f32> = textureSampleLevel(tfunTexture, clampSampler, coord, 0.0);\n coord.x = (scalar * componentSSBO.values[cNum].oScale + componentSSBO.values[cNum].oShift);\n var opacity: f32 = textureSampleLevel(ofunTexture, clampSampler, coord, 0.0).r;\n return vec4<f32>(color.rgb, opacity);\n}\n\nfn traverseMax(vTex: texture_3d<f32>, vNum: i32, cNum: i32, rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>)\n{\n // convert to tcoords and reject if outside the volume\n var numSteps: f32 = rayLengthSC/mapperUBO.SampleDistance;\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*minPosSC;\n var tpos2: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*(minPosSC + rayStepSC);\n var tstep: vec4<f32> = tpos2 - tpos;\n\n var rayBounds: vec2<f32> = adjustBounds(tpos, tstep, numSteps);\n\n // did we hit anything\n if (rayBounds.x >= rayBounds.y)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n return;\n }\n\n tpos = tpos + tstep*rayBounds.x;\n var curDist: f32 = rayBounds.x;\n var maxVal: f32 = -1.0e37;\n loop\n {\n var scalar: f32 = getTextureValue(vTex, tpos);\n if (scalar > maxVal)\n {\n maxVal = scalar;\n }\n\n // increment position\n curDist = curDist + 1.0;\n tpos = tpos + tstep;\n\n // check if we have reached a terminating condition\n if (curDist > rayBounds.y) { break; }\n }\n\n // process to get the color and opacity\n traverseVals[vNum] = getSimpleColor(maxVal, vNum, cNum);\n}\n\nfn traverseMin(vTex: texture_3d<f32>, vNum: i32, cNum: i32, rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>)\n{\n // convert to tcoords and reject if outside the volume\n var numSteps: f32 = rayLengthSC/mapperUBO.SampleDistance;\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*minPosSC;\n var tpos2: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*(minPosSC + rayStepSC);\n var tstep: vec4<f32> = tpos2 - tpos;\n\n var rayBounds: vec2<f32> = adjustBounds(tpos, tstep, numSteps);\n\n // did we hit anything\n if (rayBounds.x >= rayBounds.y)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n return;\n }\n\n tpos = tpos + tstep*rayBounds.x;\n var curDist: f32 = rayBounds.x;\n var minVal: f32 = 1.0e37;\n loop\n {\n var scalar: f32 = getTextureValue(vTex, tpos);\n if (scalar < minVal)\n {\n minVal = scalar;\n }\n\n // increment position\n curDist = curDist + 1.0;\n tpos = tpos + tstep;\n\n // check if we have reached a terminating condition\n if (curDist > rayBounds.y) { break; }\n }\n\n // process to get the color and opacity\n traverseVals[vNum] = getSimpleColor(minVal, vNum, cNum);\n}\n\nfn traverseAverage(vTex: texture_3d<f32>, vNum: i32, cNum: i32, rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>)\n{\n // convert to tcoords and reject if outside the volume\n var numSteps: f32 = rayLengthSC/mapperUBO.SampleDistance;\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*minPosSC;\n var tpos2: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*(minPosSC + rayStepSC);\n var tstep: vec4<f32> = tpos2 - tpos;\n\n var rayBounds: vec2<f32> = adjustBounds(tpos, tstep, numSteps);\n\n // did we hit anything\n if (rayBounds.x >= rayBounds.y)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n return;\n }\n\n let ipRange: vec4<f32> = volumeSSBO.values[vNum].ipScalarRange;\n tpos = tpos + tstep*rayBounds.x;\n var curDist: f32 = rayBounds.x;\n var avgVal: f32 = 0.0;\n var sampleCount: f32 = 0.0;\n loop\n {\n var sample: f32 = getTextureValue(vTex, tpos);\n // right now leave filtering off until WebGL changes get merged\n // if (ipRange.z == 0.0 || sample >= ipRange.x && sample <= ipRange.y)\n // {\n avgVal = avgVal + sample;\n sampleCount = sampleCount + 1.0;\n // }\n\n // increment position\n curDist = curDist + 1.0;\n tpos = tpos + tstep;\n\n // check if we have reached a terminating condition\n if (curDist > rayBounds.y) { break; }\n }\n\n if (sampleCount <= 0.0)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n }\n\n // process to get the color and opacity\n traverseVals[vNum] = getSimpleColor(avgVal/sampleCount, vNum, cNum);\n}\n\nfn traverseAdditive(vTex: texture_3d<f32>, vNum: i32, cNum: i32, rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>)\n{\n // convert to tcoords and reject if outside the volume\n var numSteps: f32 = rayLengthSC/mapperUBO.SampleDistance;\n var tpos: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*minPosSC;\n var tpos2: vec4<f32> = volumeSSBO.values[vNum].SCTCMatrix*(minPosSC + rayStepSC);\n var tstep: vec4<f32> = tpos2 - tpos;\n\n var rayBounds: vec2<f32> = adjustBounds(tpos, tstep, numSteps);\n\n // did we hit anything\n if (rayBounds.x >= rayBounds.y)\n {\n traverseVals[vNum] = vec4<f32>(0.0,0.0,0.0,0.0);\n return;\n }\n\n let ipRange: vec4<f32> = volumeSSBO.values[vNum].ipScalarRange;\n tpos = tpos + tstep*rayBounds.x;\n var curDist: f32 = rayBounds.x;\n var sumVal: f32 = 0.0;\n loop\n {\n var sample: f32 = getTextureValue(vTex, tpos);\n // right now leave filtering off until WebGL changes get merged\n // if (ipRange.z == 0.0 || sample >= ipRange.x && sample <= ipRange.y)\n // {\n sumVal = sumVal + sample;\n // }\n\n // increment position\n curDist = curDist + 1.0;\n tpos = tpos + tstep;\n\n // check if we have reached a terminating condition\n if (curDist > rayBounds.y) { break; }\n }\n\n // process to get the color and opacity\n traverseVals[vNum] = getSimpleColor(sumVal, vNum, cNum);\n}\n\nfn composite(rayLengthSC: f32, minPosSC: vec4<f32>, rayStepSC: vec4<f32>) -> vec4<f32>\n{\n // initial ray position is at the beginning\n var rayPosSC: vec4<f32> = minPosSC;\n\n // how many rows (tfuns) do we have in our tfunTexture\n var tfunRows: f32 = f32(textureDimensions(tfunTexture).y);\n\n var curDist: f32 = 0.0;\n var computedColor: vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n var sampleColor: vec4<f32>;\n//VTK::Volume::TraverseCalls\n\n loop\n {\n // for each volume, sample and accumulate color\n//VTK::Volume::CompositeCalls\n\n // increment position\n curDist = curDist + mapperUBO.SampleDistance;\n rayPosSC = rayPosSC + rayStepSC;\n\n // check if we have reached a terminating condition\n if (curDist > rayLengthSC) { break; }\n if (computedColor.a > 0.98) { break; }\n }\n return computedColor;\n}\n\n[[stage(fragment)]]\nfn main(\n//VTK::IOStructs::Input\n)\n//VTK::IOStructs::Output\n{\n var output: fragmentOutput;\n\n var rayMax: f32 = textureSampleLevel(maxTexture, clampSampler, input.tcoordVS, 0.0).r;\n var rayMin: f32 = textureSampleLevel(minTexture, clampSampler, input.tcoordVS, 0.0).r;\n\n // discard empty rays\n if (rayMax <= rayMin) { discard; }\n else\n {\n // compute start and end ray positions in view coordinates\n var minPosSC: vec4<f32> = rendererUBO.PCSCMatrix*vec4<f32>(2.0 * input.tcoordVS.x - 1.0, 1.0 - 2.0 * input.tcoordVS.y, rayMax, 1.0);\n minPosSC = minPosSC * (1.0 / minPosSC.w);\n var maxPosSC: vec4<f32> = rendererUBO.PCSCMatrix*vec4<f32>(2.0 * input.tcoordVS.x - 1.0, 1.0 - 2.0 * input.tcoordVS.y, rayMin, 1.0);\n maxPosSC = maxPosSC * (1.0 / maxPosSC.w);\n\n var rayLengthSC: f32 = distance(minPosSC.xyz, maxPosSC.xyz);\n var rayStepSC: vec4<f32> = (maxPosSC - minPosSC)*(mapperUBO.SampleDistance/rayLengthSC);\n rayStepSC.w = 0.0;\n\n var computedColor: vec4<f32>;\n\n//VTK::Volume::Loop\n\n//VTK::RenderEncoder::Impl\n }\n\n return output;\n}\n";
13
13
  var tmpMat4 = new Float64Array(16);
14
14
  var tmp2Mat4 = new Float64Array(16); // ----------------------------------------------------------------------------
15
15
  // vtkWebGPUVolumePassFSQ methods
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "22.0.2",
3
+ "version": "22.1.3",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",