@kitware/vtk.js 22.0.2 → 22.1.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.
@@ -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,7 @@ 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 vtkWebGPUFullScreenQuad from './FullScreenQuad.js';
11
12
  import vtkWebGPUVolumePassFSQ from './VolumePassFSQ.js';
12
13
  import { f as distance2BetweenPoints } from '../../Common/Core/Math/index.js';
13
14
 
@@ -32,6 +33,7 @@ var BufferUsage = vtkWebGPUBufferManager.BufferUsage,
32
33
 
33
34
  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
35
  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";
36
+ 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, input.tcoordVS);\n\n //VTK::RenderEncoder::Impl\n return output;\n}\n";
35
37
  /* eslint-disable no-undef */
36
38
 
37
39
  /* eslint-disable no-bitwise */
@@ -39,7 +41,41 @@ var DepthBoundsFS = "\n//VTK::Renderer::Dec\n\n//VTK::Select::Dec\n\n//VTK::Volu
39
41
 
40
42
  function vtkWebGPUVolumePass(publicAPI, model) {
41
43
  // Set our className
42
- model.classHierarchy.push('vtkWebGPUVolumePass');
44
+ model.classHierarchy.push('vtkWebGPUVolumePass'); // create the required textures, encoders, FSQ etc
45
+
46
+ publicAPI.initialize = function (viewNode) {
47
+ if (!model._mergeEncoder) {
48
+ publicAPI.createMergeEncoder(viewNode);
49
+ }
50
+
51
+ if (!model._clearEncoder) {
52
+ publicAPI.createClearEncoder(viewNode);
53
+ }
54
+
55
+ if (!model._copyEncoder) {
56
+ publicAPI.createCopyEncoder(viewNode);
57
+ }
58
+
59
+ if (!model._depthRangeEncoder) {
60
+ publicAPI.createDepthRangeEncoder(viewNode);
61
+ }
62
+
63
+ if (!model.fullScreenQuad) {
64
+ model.fullScreenQuad = vtkWebGPUVolumePassFSQ.newInstance();
65
+ model.fullScreenQuad.setDevice(viewNode.getDevice());
66
+ model.fullScreenQuad.setTextureViews(_toConsumableArray(model._depthRangeEncoder.getColorTextureViews()));
67
+ }
68
+
69
+ if (!model._volumeCopyQuadQuad) {
70
+ model._volumeCopyQuad = vtkWebGPUFullScreenQuad.newInstance();
71
+
72
+ model._volumeCopyQuad.setPipelineHash('volpassfsq');
73
+
74
+ model._volumeCopyQuad.setDevice(viewNode.getDevice());
75
+
76
+ model._volumeCopyQuad.setFragmentShaderTemplate(volumeCopyFragTemplate);
77
+ }
78
+ };
43
79
 
44
80
  publicAPI.traverse = function (renNode, viewNode) {
45
81
  if (model.deleted) {
@@ -47,29 +83,25 @@ function vtkWebGPUVolumePass(publicAPI, model) {
47
83
  } // we just render our delegates in order
48
84
 
49
85
 
50
- model.currentParent = viewNode; // then perform the ray casting using the depth bounds texture
51
-
52
- if (!model.finalEncoder) {
53
- publicAPI.createFinalEncoder(viewNode);
54
- } // first render the boxes to generate a min max depth
55
- // map for all the volumes
86
+ model.currentParent = viewNode; // create stuff we need
56
87
 
88
+ publicAPI.initialize(viewNode); // determine if we are rendering a small size
57
89
 
58
- publicAPI.renderDepthBounds(renNode, viewNode);
90
+ publicAPI.computeTiming(viewNode); // first render the boxes to generate a min max depth
91
+ // map for all the volumes
59
92
 
60
- if (!model.fullScreenQuad) {
61
- model.fullScreenQuad = vtkWebGPUVolumePassFSQ.newInstance();
62
- model.fullScreenQuad.setDevice(viewNode.getDevice());
63
- model.fullScreenQuad.setTextureViews(_toConsumableArray(model.depthRangeEncoder.getColorTextureViews()));
64
- }
93
+ publicAPI.renderDepthBounds(renNode, viewNode); // always mark true
65
94
 
66
- var device = viewNode.getDevice(); // -4 because we use know we use textures for min, max, ofun and tfun
95
+ model._firstGroup = true;
96
+ var device = viewNode.getDevice(); // determine how many volumes we can render at a time. We subtract
97
+ // 4 because we use know we use textures for min, max, ofun and tfun
67
98
 
68
- var maxVolumes = device.getHandle().limits.maxSampledTexturesPerShaderStage - 4;
69
- var cameraPos = renNode.getRenderable().getActiveCamera().getPosition();
99
+ var maxVolumes = device.getHandle().limits.maxSampledTexturesPerShaderStage - 4; // if we have to make multiple passes then break the volumes up into groups
100
+ // rendered from farthest to closest
70
101
 
71
102
  if (model.volumes.length > maxVolumes) {
72
- // sort from back to front based on volume centroid
103
+ var cameraPos = renNode.getRenderable().getActiveCamera().getPosition(); // sort from back to front based on volume centroid
104
+
73
105
  var distances = [];
74
106
 
75
107
  for (var v = 0; v < model.volumes.length; v++) {
@@ -94,26 +126,116 @@ function vtkWebGPUVolumePass(publicAPI, model) {
94
126
  volumesToRender.push(model.volumes[volumeOrder[_v]]);
95
127
 
96
128
  if (volumesToRender.length >= chunkSize) {
97
- publicAPI.finalPass(viewNode, renNode, volumesToRender);
129
+ publicAPI.rayCastPass(viewNode, renNode, volumesToRender);
98
130
  volumesToRender = [];
99
131
  chunkSize = maxVolumes;
132
+ model._firstGroup = false;
100
133
  }
101
134
  }
102
135
  } else {
103
- publicAPI.finalPass(viewNode, renNode, model.volumes);
136
+ // if not rendering in chunks then just draw all of them at once
137
+ publicAPI.rayCastPass(viewNode, renNode, model.volumes);
138
+ } // copy back to the original color buffer
139
+
140
+
141
+ if (model.small) {
142
+ model._volumeCopyQuad.setTextureViews([model._smallColorTextureView]);
143
+ } else {
144
+ model._volumeCopyQuad.setTextureViews([model._largeColorTextureView]);
145
+ } // final composite
146
+
147
+
148
+ model._copyEncoder.setColorTextureView(0, model.colorTextureView);
149
+
150
+ model._copyEncoder.attachTextureViews();
151
+
152
+ renNode.setRenderEncoder(model._copyEncoder);
153
+
154
+ model._copyEncoder.begin(viewNode.getCommandEncoder());
155
+
156
+ renNode.scissorAndViewport(model._copyEncoder);
157
+
158
+ model._volumeCopyQuad.setWebGPURenderer(renNode);
159
+
160
+ model._volumeCopyQuad.render(model._copyEncoder, viewNode.getDevice());
161
+
162
+ model._copyEncoder.end();
163
+ }; // unsubscribe from our listeners
164
+
165
+
166
+ publicAPI.delete = macro.chain(function () {
167
+ if (model._animationRateSubscription) {
168
+ model._animationRateSubscription.unsubscribe();
169
+
170
+ model._animationRateSubscription = null;
171
+ }
172
+ }, publicAPI.delete);
173
+
174
+ publicAPI.computeTiming = function (viewNode) {
175
+ model.small = false;
176
+ var rwi = viewNode.getRenderable().getInteractor();
177
+
178
+ if (rwi.isAnimating() && model._lastScale > 1.5) {
179
+ model.small = true;
180
+ }
181
+
182
+ if (model.small) {
183
+ model._activeColorTextureView = model._smallColorTextureView;
184
+ } else {
185
+ model._largeColorTexture.resize(viewNode.getCanvas().width, viewNode.getCanvas().height);
186
+
187
+ model._activeColorTextureView = model._largeColorTextureView;
188
+ }
189
+
190
+ if (!model._animationRateSubscription) {
191
+ // when the animation frame rate changes recompute the scale factor
192
+ model._animationRateSubscription = rwi.onAnimationFrameRateUpdate(function () {
193
+ var frate = rwi.getRecentAnimationFrameRate();
194
+ var targetScale = model._lastScale * rwi.getDesiredUpdateRate() / frate; // Do we need a resize? The values below are to provide some stickyness
195
+ // so that we are not changing texture size every second. Instead the targhet scale
196
+ // has to be a certain amount larger or smaller than the current value to force a
197
+ // change in texture size
198
+
199
+ if (targetScale > 1.4 * model._lastScale || targetScale < 0.7 * model._lastScale) {
200
+ model._lastScale = targetScale; // clamp scale to some reasonable values.
201
+ // Below 1.5 we will just be using full resolution as that is close enough
202
+ // Above 400 seems like a lot so we limit to that 1/20th per axis
203
+
204
+ if (model._lastScale > 400) {
205
+ model._lastScale = 400;
206
+ }
207
+
208
+ if (model._lastScale < 1.5) {
209
+ model._lastScale = 1.5;
210
+ } else {
211
+ var targetSmallWidth = 64 * Math.ceil(viewNode.getCanvas().width / (Math.sqrt(model._lastScale) * 64));
212
+ var targetSmallHeight = Math.ceil(targetSmallWidth * viewNode.getCanvas().height / viewNode.getCanvas().width);
213
+
214
+ model._smallColorTexture.resize(targetSmallWidth, targetSmallHeight);
215
+ }
216
+ }
217
+ });
104
218
  }
105
219
  };
106
220
 
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);
221
+ publicAPI.rayCastPass = function (viewNode, renNode, volumes) {
222
+ var encoder = model._firstGroup ? model._clearEncoder : model._mergeEncoder;
223
+ encoder.setColorTextureView(0, model._activeColorTextureView);
224
+ encoder.attachTextureViews();
225
+ renNode.setRenderEncoder(encoder);
226
+ encoder.begin(viewNode.getCommandEncoder());
227
+
228
+ var width = model._activeColorTextureView.getTexture().getWidth();
229
+
230
+ var height = model._activeColorTextureView.getTexture().getHeight();
231
+
232
+ encoder.getHandle().setViewport(0, 0, width, height, 0.0, 1.0); // set scissor
233
+
234
+ encoder.getHandle().setScissorRect(0, 0, width, height);
113
235
  model.fullScreenQuad.setWebGPURenderer(renNode);
114
236
  model.fullScreenQuad.setVolumes(volumes);
115
- model.fullScreenQuad.render(model.finalEncoder, viewNode.getDevice());
116
- model.finalEncoder.end();
237
+ model.fullScreenQuad.render(encoder, viewNode.getDevice());
238
+ encoder.end();
117
239
  };
118
240
 
119
241
  publicAPI.renderDepthBounds = function (renNode, viewNode) {
@@ -211,55 +333,32 @@ function vtkWebGPUVolumePass(publicAPI, model) {
211
333
  publicAPI.drawDepthRange = function (renNode, viewNode) {
212
334
  var device = viewNode.getDevice(); // copy current depth buffer to
213
335
 
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);
336
+ model._depthRangeTexture.resizeToMatch(model.colorTextureView.getTexture());
236
337
 
237
- model._mapper.setDevice(viewNode.getDevice());
338
+ model._depthRangeTexture2.resizeToMatch(model.colorTextureView.getTexture());
238
339
 
239
- model._mapper.setTextureViews([model.depthTextureView]);
240
- } else {
241
- model.depthRangeTexture.resizeToMatch(model.colorTextureView.getTexture());
242
- model.depthRangeTexture2.resizeToMatch(model.colorTextureView.getTexture());
243
- }
340
+ model._depthRangeEncoder.attachTextureViews();
244
341
 
245
- model.depthRangeEncoder.attachTextureViews();
246
342
  publicAPI.setCurrentOperation('volumeDepthRangePass');
247
- renNode.setRenderEncoder(model.depthRangeEncoder);
343
+ renNode.setRenderEncoder(model._depthRangeEncoder);
248
344
  renNode.volumeDepthRangePass(true);
249
345
 
250
346
  model._mapper.setWebGPURenderer(renNode);
251
347
 
252
- model._mapper.build(model.depthRangeEncoder, device);
348
+ model._mapper.build(model._depthRangeEncoder, device);
253
349
 
254
350
  model._mapper.registerToDraw();
255
351
 
256
352
  renNode.volumeDepthRangePass(false);
257
353
  };
258
354
 
259
- publicAPI.createDepthRangeEncoder = function () {
260
- model.depthRangeEncoder = vtkWebGPURenderEncoder.newInstance();
261
- model.depthRangeEncoder.setPipelineHash('volr');
262
- model.depthRangeEncoder.setReplaceShaderCodeFunction(function (pipeline) {
355
+ publicAPI.createDepthRangeEncoder = function (viewNode) {
356
+ var device = viewNode.getDevice();
357
+ model._depthRangeEncoder = vtkWebGPURenderEncoder.newInstance();
358
+
359
+ model._depthRangeEncoder.setPipelineHash('volr');
360
+
361
+ model._depthRangeEncoder.setReplaceShaderCodeFunction(function (pipeline) {
263
362
  var fDesc = pipeline.getShaderDescription('fragment');
264
363
  fDesc.addOutput('vec4<f32>', 'outColor1');
265
364
  fDesc.addOutput('vec4<f32>', 'outColor2');
@@ -267,7 +366,8 @@ function vtkWebGPUVolumePass(publicAPI, model) {
267
366
  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
367
  fDesc.setCode(code);
269
368
  });
270
- model.depthRangeEncoder.setDescription({
369
+
370
+ model._depthRangeEncoder.setDescription({
271
371
  colorAttachments: [{
272
372
  view: null,
273
373
  loadValue: [0.0, 0.0, 0.0, 0.0],
@@ -278,7 +378,8 @@ function vtkWebGPUVolumePass(publicAPI, model) {
278
378
  storeOp: 'store'
279
379
  }]
280
380
  });
281
- model.depthRangeEncoder.setPipelineSettings({
381
+
382
+ model._depthRangeEncoder.setPipelineSettings({
282
383
  primitive: {
283
384
  cullMode: 'none'
284
385
  },
@@ -313,27 +414,184 @@ function vtkWebGPUVolumePass(publicAPI, model) {
313
414
  }
314
415
  }]
315
416
  }
417
+ }); // and the textures it needs
418
+
419
+
420
+ model._depthRangeTexture = vtkWebGPUTexture.newInstance();
421
+
422
+ model._depthRangeTexture.create(device, {
423
+ width: viewNode.getCanvas().width,
424
+ height: viewNode.getCanvas().height,
425
+ format: 'r16float',
426
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
427
+ });
428
+
429
+ var maxView = model._depthRangeTexture.createView();
430
+
431
+ maxView.setName('maxTexture');
432
+
433
+ model._depthRangeEncoder.setColorTextureView(0, maxView);
434
+
435
+ model._depthRangeTexture2 = vtkWebGPUTexture.newInstance();
436
+
437
+ model._depthRangeTexture2.create(device, {
438
+ width: viewNode.getCanvas().width,
439
+ height: viewNode.getCanvas().height,
440
+ format: 'r16float',
441
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
442
+ });
443
+
444
+ var minView = model._depthRangeTexture2.createView();
445
+
446
+ minView.setName('minTexture');
447
+
448
+ model._depthRangeEncoder.setColorTextureView(1, minView);
449
+
450
+ model._mapper.setDevice(viewNode.getDevice());
451
+
452
+ model._mapper.setTextureViews([model.depthTextureView]);
453
+ };
454
+
455
+ publicAPI.createClearEncoder = function (viewNode) {
456
+ model._smallColorTexture = vtkWebGPUTexture.newInstance(); // multiple of 64 pixels to help with texture alignment/size issues
457
+ // as webgpu textures have to be multiples of 256 bytes wide
458
+ // for data transfers (just in case)
459
+
460
+ var targetSmallWidth = 64 * Math.ceil(0.7 * viewNode.getCanvas().width / 64);
461
+
462
+ model._smallColorTexture.create(viewNode.getDevice(), {
463
+ width: targetSmallWidth,
464
+ height: Math.ceil(targetSmallWidth * viewNode.getCanvas().height / viewNode.getCanvas().width),
465
+ format: 'bgra8unorm',
466
+
467
+ /* eslint-disable no-undef */
468
+
469
+ /* eslint-disable no-bitwise */
470
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
471
+ });
472
+
473
+ model._smallColorTextureView = model._smallColorTexture.createView();
474
+
475
+ model._smallColorTextureView.setName('volumePassSmallColorTexture');
476
+
477
+ model._smallColorTextureView.addSampler(viewNode.getDevice(), {
478
+ minFilter: 'linear',
479
+ magFilter: 'linear'
480
+ });
481
+
482
+ model._largeColorTexture = vtkWebGPUTexture.newInstance();
483
+
484
+ model._largeColorTexture.create(viewNode.getDevice(), {
485
+ width: viewNode.getCanvas().width,
486
+ height: viewNode.getCanvas().height,
487
+ format: 'bgra8unorm',
488
+
489
+ /* eslint-disable no-undef */
490
+
491
+ /* eslint-disable no-bitwise */
492
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
493
+ });
494
+
495
+ model._largeColorTextureView = model._largeColorTexture.createView();
496
+
497
+ model._largeColorTextureView.setName('volumePassSmallColorTexture');
498
+
499
+ model._largeColorTextureView.addSampler(viewNode.getDevice(), {
500
+ minFilter: 'linear',
501
+ magFilter: 'linear'
502
+ });
503
+
504
+ model._clearEncoder = vtkWebGPURenderEncoder.newInstance();
505
+
506
+ model._clearEncoder.setDescription({
507
+ colorAttachments: [{
508
+ view: null,
509
+ loadValue: [0.0, 0.0, 0.0, 0.0],
510
+ storeOp: 'store'
511
+ }]
512
+ });
513
+
514
+ model._clearEncoder.setPipelineHash('volpf');
515
+
516
+ model._clearEncoder.setPipelineSettings({
517
+ primitive: {
518
+ cullMode: 'none'
519
+ },
520
+ fragment: {
521
+ targets: [{
522
+ format: 'bgra8unorm',
523
+ blend: {
524
+ color: {
525
+ srcFactor: 'src-alpha',
526
+ dstFactor: 'one-minus-src-alpha'
527
+ },
528
+ alpha: {
529
+ srcfactor: 'one',
530
+ dstFactor: 'one-minus-src-alpha'
531
+ }
532
+ }
533
+ }]
534
+ }
316
535
  });
317
536
  };
318
537
 
319
- publicAPI.createFinalEncoder = function (viewNode) {
320
- model.finalEncoder = vtkWebGPURenderEncoder.newInstance();
321
- model.finalEncoder.setDescription({
538
+ publicAPI.createCopyEncoder = function (viewNode) {
539
+ model._copyEncoder = vtkWebGPURenderEncoder.newInstance();
540
+
541
+ model._copyEncoder.setDescription({
322
542
  colorAttachments: [{
323
543
  view: null,
324
544
  loadValue: 'load',
325
545
  storeOp: 'store'
326
546
  }]
327
547
  });
328
- model.finalEncoder.setReplaceShaderCodeFunction(function (pipeline) {
548
+
549
+ model._copyEncoder.setPipelineHash('volcopypf');
550
+
551
+ model._copyEncoder.setPipelineSettings({
552
+ primitive: {
553
+ cullMode: 'none'
554
+ },
555
+ fragment: {
556
+ targets: [{
557
+ format: 'bgra8unorm',
558
+ blend: {
559
+ color: {
560
+ srcFactor: 'one',
561
+ dstFactor: 'one-minus-src-alpha'
562
+ },
563
+ alpha: {
564
+ srcfactor: 'one',
565
+ dstFactor: 'one-minus-src-alpha'
566
+ }
567
+ }
568
+ }]
569
+ }
570
+ });
571
+ };
572
+
573
+ publicAPI.createMergeEncoder = function (viewNode) {
574
+ model._mergeEncoder = vtkWebGPURenderEncoder.newInstance();
575
+
576
+ model._mergeEncoder.setDescription({
577
+ colorAttachments: [{
578
+ view: null,
579
+ loadValue: 'load',
580
+ storeOp: 'store'
581
+ }]
582
+ });
583
+
584
+ model._mergeEncoder.setReplaceShaderCodeFunction(function (pipeline) {
329
585
  var fDesc = pipeline.getShaderDescription('fragment');
330
586
  fDesc.addOutput('vec4<f32>', 'outColor');
331
587
  var code = fDesc.getCode();
332
- code = vtkWebGPUShaderCache.substitute(code, '//VTK::RenderEncoder::Impl', ['output.outColor = vec4<f32>(computedColor.rgb*computedColor.a, computedColor.a);']).result;
588
+ code = vtkWebGPUShaderCache.substitute(code, '//VTK::RenderEncoder::Impl', ['output.outColor = vec4<f32>(computedColor.rgb, computedColor.a);']).result;
333
589
  fDesc.setCode(code);
334
590
  });
335
- model.finalEncoder.setPipelineHash('volpf');
336
- model.finalEncoder.setPipelineSettings({
591
+
592
+ model._mergeEncoder.setPipelineHash('volpf');
593
+
594
+ model._mergeEncoder.setPipelineSettings({
337
595
  primitive: {
338
596
  cullMode: 'none'
339
597
  },
@@ -342,7 +600,7 @@ function vtkWebGPUVolumePass(publicAPI, model) {
342
600
  format: 'bgra8unorm',
343
601
  blend: {
344
602
  color: {
345
- srcFactor: 'one',
603
+ srcFactor: 'src-alpha',
346
604
  dstFactor: 'one-minus-src-alpha'
347
605
  },
348
606
  alpha: {
@@ -387,6 +645,7 @@ function extend(publicAPI, model) {
387
645
  Object.assign(model, DEFAULT_VALUES, initialValues); // Build VTK API
388
646
 
389
647
  vtkRenderPass.extend(publicAPI, model, initialValues);
648
+ model._lastScale = 2.0;
390
649
  model._mapper = vtkWebGPUMapperHelper.newInstance();
391
650
 
392
651
  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.0",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",