@kitware/vtk.js 32.11.0 → 32.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { vtkObject } from './../../interfaces';
2
2
  import { Vector3 } from './../../types';
3
- import vtkActor from './Actor';
3
+ import vtkProp3D from './Prop3D';
4
4
  import vtkRenderer from './Renderer';
5
5
 
6
6
  /**
@@ -10,8 +10,8 @@ export interface IAbstractPickerInitialValues {
10
10
  renderer?: vtkRenderer;
11
11
  selectionPoint?: Vector3;
12
12
  pickPosition?: Vector3;
13
- pickFromList?: number;
14
- pickList?: vtkActor[];
13
+ pickFromList?: boolean;
14
+ pickList?: vtkProp3D[];
15
15
  }
16
16
 
17
17
  /**
@@ -20,15 +20,15 @@ export interface IAbstractPickerInitialValues {
20
20
  export interface vtkAbstractPicker extends vtkObject {
21
21
  /**
22
22
  *
23
- * @param {vtkActor} actor
23
+ * @param {vtkProp3D} prop
24
24
  */
25
- addPickList(actor: vtkActor): void;
25
+ addPickList(prop: vtkProp3D): void;
26
26
 
27
27
  /**
28
28
  *
29
- * @param {vtkActor} actor
29
+ * @param {vtkProp3D} prop
30
30
  */
31
- deletePickList(actor: vtkActor): void;
31
+ deletePickList(prop: vtkProp3D): void;
32
32
 
33
33
  /**
34
34
  *
@@ -38,7 +38,7 @@ export interface vtkAbstractPicker extends vtkObject {
38
38
  /**
39
39
  *
40
40
  */
41
- getPickList(): boolean;
41
+ getPickList(): vtkProp3D[];
42
42
 
43
43
  /**
44
44
  * Get the picked position
@@ -82,17 +82,17 @@ export interface vtkAbstractPicker extends vtkObject {
82
82
 
83
83
  /**
84
84
  *
85
- * @param {Number} pickFromList
86
- * @default 0
85
+ * @param {Boolean} pickFromList
86
+ * @default false
87
87
  */
88
- setPickFromList(pickFromList: number): boolean;
88
+ setPickFromList(pickFromList: boolean): boolean;
89
89
 
90
90
  /**
91
91
  *
92
- * @param {vtkActor[]} pickList
92
+ * @param {vtkProp3D[]} pickList
93
93
  * @default []
94
94
  */
95
- setPickList(pickList: vtkActor[]): boolean;
95
+ setPickList(pickList: vtkProp3D[]): boolean;
96
96
  }
97
97
 
98
98
  /**
@@ -38,7 +38,7 @@ const DEFAULT_VALUES = {
38
38
  renderer: null,
39
39
  selectionPoint: [0.0, 0.0, 0.0],
40
40
  pickPosition: [0.0, 0.0, 0.0],
41
- pickFromList: 0,
41
+ pickFromList: false,
42
42
  pickList: []
43
43
  };
44
44
 
@@ -1,5 +1,5 @@
1
1
  import { mat3, mat4, quat, vec3 } from 'gl-matrix';
2
- import { Nullable } from './../../types';
2
+ import { Extent, Nullable } from './../../types';
3
3
  import { vtkOutputPort } from './../../interfaces';
4
4
  import vtkAbstractMapper3D, {
5
5
  IAbstractMapper3DInitialValues,
@@ -307,6 +307,25 @@ export interface vtkImageCPRMapper
307
307
  * @param imageData
308
308
  */
309
309
  setImageConnection(imageData: vtkOutputPort): void;
310
+
311
+ /**
312
+ * Tells the mapper to only update the specified extents.
313
+ *
314
+ * If there are zero extents, the mapper updates the entire volume texture.
315
+ * Otherwise, the mapper will only update the texture by the specified extents
316
+ * during the next render call.
317
+ *
318
+ * This array is cleared after a successful render.
319
+ * @param extents
320
+ */
321
+ setUpdatedExtents(extents: Extent[]): boolean;
322
+
323
+ /**
324
+ * Retrieves the updated extents.
325
+ *
326
+ * This array is cleared after every successful render.
327
+ */
328
+ getUpdatedExtents(): Extent[];
310
329
  }
311
330
 
312
331
  /**
@@ -270,7 +270,7 @@ function vtkImageCPRMapper(publicAPI, model) {
270
270
  // Object factory
271
271
  // ----------------------------------------------------------------------------
272
272
 
273
- const DEFAULT_VALUES = {
273
+ const defaultValues = initialValues => ({
274
274
  width: 10,
275
275
  uniformOrientation: [0, 0, 0, 1],
276
276
  useUniformOrientation: false,
@@ -282,14 +282,16 @@ const DEFAULT_VALUES = {
282
282
  normalDirection: [0, 0, 1],
283
283
  projectionSlabThickness: 1,
284
284
  projectionSlabNumberOfSamples: 1,
285
- projectionMode: ProjectionMode.MAX
286
- };
285
+ projectionMode: ProjectionMode.MAX,
286
+ updatedExtents: [],
287
+ ...initialValues
288
+ });
287
289
 
288
290
  // ----------------------------------------------------------------------------
289
291
 
290
292
  function extend(publicAPI, model) {
291
293
  let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
292
- Object.assign(model, DEFAULT_VALUES, initialValues);
294
+ Object.assign(model, defaultValues(initialValues));
293
295
 
294
296
  // Inheritance
295
297
  vtkAbstractImageMapper.extend(publicAPI, model, initialValues);
@@ -302,7 +304,7 @@ function extend(publicAPI, model) {
302
304
  });
303
305
 
304
306
  // Setters and getters
305
- macro.setGet(publicAPI, model, ['width', 'uniformOrientation', 'useUniformOrientation', 'centerPoint', 'preferSizeOverAccuracy', 'orientationArrayName', 'tangentDirection', 'bitangentDirection', 'normalDirection', 'projectionSlabThickness', 'projectionSlabNumberOfSamples', 'projectionMode']);
307
+ macro.setGet(publicAPI, model, ['width', 'uniformOrientation', 'useUniformOrientation', 'centerPoint', 'preferSizeOverAccuracy', 'orientationArrayName', 'tangentDirection', 'bitangentDirection', 'normalDirection', 'projectionSlabThickness', 'projectionSlabNumberOfSamples', 'projectionMode', 'updatedExtents']);
306
308
  CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);
307
309
 
308
310
  // Object methods
@@ -1,10 +1,9 @@
1
1
  import vtkAbstractImageMapper, {
2
2
  IAbstractImageMapperInitialValues,
3
3
  } from './AbstractImageMapper';
4
- import vtkImageData from './../../Common/DataModel/ImageData';
5
4
  import vtkPlane from './../../Common/DataModel/Plane';
6
5
  import vtkPolyData from './../../Common/DataModel/PolyData';
7
- import { Bounds, Nullable, Vector3 } from './../../types';
6
+ import { Bounds, Extent } from './../../types';
8
7
  import { SlabTypes } from './ImageResliceMapper/Constants';
9
8
  import CoincidentTopologyHelper, {
10
9
  StaticCoincidentTopologyMethods,
@@ -116,6 +115,25 @@ export interface vtkImageResliceMapper
116
115
  * @param {vtkPolyData} slicePolyData The polydata to slice the volume with. Default: null
117
116
  */
118
117
  setSlicePolyData(slicePolyData: vtkPolyData): boolean;
118
+
119
+ /**
120
+ * Tells the mapper to only update the specified extents.
121
+ *
122
+ * If there are zero extents, the mapper updates the entire volume texture.
123
+ * Otherwise, the mapper will only update the texture by the specified extents
124
+ * during the next render call.
125
+ *
126
+ * This array is cleared after a successful render.
127
+ * @param extents
128
+ */
129
+ setUpdatedExtents(extents: Extent[]): boolean;
130
+
131
+ /**
132
+ * Retrieves the updated extents.
133
+ *
134
+ * This array is cleared after every successful render.
135
+ */
136
+ getUpdatedExtents(): Extent[];
119
137
  }
120
138
 
121
139
  /**
@@ -38,23 +38,25 @@ function vtkImageResliceMapper(publicAPI, model) {
38
38
  // Object factory
39
39
  // ----------------------------------------------------------------------------
40
40
 
41
- const DEFAULT_VALUES = {
41
+ const defaultValues = initialValues => ({
42
42
  slabThickness: 0.0,
43
43
  slabTrapezoidIntegration: 0,
44
44
  slabType: SlabTypes.MEAN,
45
45
  slicePlane: null,
46
- slicePolyData: null
47
- };
46
+ slicePolyData: null,
47
+ updatedExtents: [],
48
+ ...initialValues
49
+ });
48
50
 
49
51
  // ----------------------------------------------------------------------------
50
52
 
51
53
  function extend(publicAPI, model) {
52
54
  let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
53
- Object.assign(model, DEFAULT_VALUES, initialValues);
55
+ Object.assign(model, defaultValues(initialValues));
54
56
 
55
57
  // Build VTK API
56
58
  vtkAbstractImageMapper.extend(publicAPI, model, initialValues);
57
- macro.setGet(publicAPI, model, ['slabThickness', 'slabTrapezoidIntegration', 'slabType', 'slicePlane', 'slicePolyData']);
59
+ macro.setGet(publicAPI, model, ['slabThickness', 'slabTrapezoidIntegration', 'slabType', 'slicePlane', 'slicePolyData', 'updatedExtents']);
58
60
  CoincidentTopologyHelper.implementCoincidentTopologyMethods(publicAPI, model);
59
61
 
60
62
  // Object methods
@@ -1,5 +1,5 @@
1
1
  import vtkPiecewiseFunction from './../../Common/DataModel/PiecewiseFunction';
2
- import { Bounds, Range } from './../../types';
2
+ import { Bounds, Range, Extent } from './../../types';
3
3
  import vtkAbstractMapper3D, {
4
4
  IAbstractMapper3DInitialValues,
5
5
  } from './AbstractMapper3D';
@@ -281,6 +281,39 @@ export interface vtkVolumeMapper extends vtkAbstractMapper3D {
281
281
  */
282
282
  setLAOKernelRadius(LAOKernelRadius: number): void;
283
283
 
284
+ /**
285
+ * Set kernel size for local ambient occlusion. It specifies the number of rays that are randomly sampled in the hemisphere.
286
+ * Value is clipped between 1 and 32.
287
+ * @param LAOKernelSize
288
+ */
289
+ setLAOKernelSize(LAOKernelSize: number): void;
290
+
291
+ /**
292
+ * Set kernel radius for local ambient occlusion. It specifies the number of samples that are considered on each random ray.
293
+ * Value must be greater than or equal to 1.
294
+ * @param LAOKernelRadius
295
+ */
296
+ setLAOKernelRadius(LAOKernelRadius: number): void;
297
+
298
+ /**
299
+ * Tells the mapper to only update the specified extents.
300
+ *
301
+ * If there are zero extents, the mapper updates the entire volume texture.
302
+ * Otherwise, the mapper will only update the texture by the specified extents
303
+ * during the next render call.
304
+ *
305
+ * This array is cleared after a successful render.
306
+ * @param extents
307
+ */
308
+ setUpdatedExtents(extents: Extent[]): boolean;
309
+
310
+ /**
311
+ * Retrieves the updated extents.
312
+ *
313
+ * This array is cleared after every successful render.
314
+ */
315
+ getUpdatedExtents(): Extent[];
316
+
284
317
  /**
285
318
  *
286
319
  */
@@ -101,7 +101,7 @@ function vtkVolumeMapper(publicAPI, model) {
101
101
  // ----------------------------------------------------------------------------
102
102
 
103
103
  // TODO: what values to use for averageIPScalarRange to get GLSL to use max / min values like [-Math.inf, Math.inf]?
104
- const DEFAULT_VALUES = {
104
+ const defaultValues = initialValues => ({
105
105
  bounds: [1, -1, 1, -1, 1, -1],
106
106
  sampleDistance: 1.0,
107
107
  imageSampleDistance: 1.0,
@@ -124,16 +124,18 @@ const DEFAULT_VALUES = {
124
124
  // local ambient occlusion
125
125
  localAmbientOcclusion: false,
126
126
  LAOKernelSize: 15,
127
- LAOKernelRadius: 7
128
- };
127
+ LAOKernelRadius: 7,
128
+ updatedExtents: [],
129
+ ...initialValues
130
+ });
129
131
 
130
132
  // ----------------------------------------------------------------------------
131
133
 
132
134
  function extend(publicAPI, model) {
133
135
  let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
134
- Object.assign(model, DEFAULT_VALUES, initialValues);
136
+ Object.assign(model, defaultValues(initialValues));
135
137
  vtkAbstractMapper3D.extend(publicAPI, model, initialValues);
136
- macro.setGet(publicAPI, model, ['sampleDistance', 'imageSampleDistance', 'maximumSamplesPerRay', 'autoAdjustSampleDistances', 'initialInteractionScale', 'interactionSampleDistanceFactor', 'blendMode', 'filterMode', 'preferSizeOverAccuracy', 'computeNormalFromOpacity', 'volumetricScatteringBlending', 'globalIlluminationReach', 'volumeShadowSamplingDistFactor', 'anisotropy', 'localAmbientOcclusion', 'LAOKernelSize', 'LAOKernelRadius']);
138
+ macro.setGet(publicAPI, model, ['sampleDistance', 'imageSampleDistance', 'maximumSamplesPerRay', 'autoAdjustSampleDistances', 'initialInteractionScale', 'interactionSampleDistanceFactor', 'blendMode', 'filterMode', 'preferSizeOverAccuracy', 'computeNormalFromOpacity', 'volumetricScatteringBlending', 'globalIlluminationReach', 'volumeShadowSamplingDistFactor', 'anisotropy', 'localAmbientOcclusion', 'LAOKernelSize', 'LAOKernelRadius', 'updatedExtents']);
137
139
  macro.setGetArray(publicAPI, model, ['ipScalarRange'], 2);
138
140
  macro.event(publicAPI, model, 'lightingActivated');
139
141
 
@@ -156,6 +156,8 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
156
156
  const cachedScalarsEntry = model._openGLRenderWindow.getGraphicsResourceForObject(scalars);
157
157
  const volumeTextureHash = getImageDataHash(image, scalars);
158
158
  const reBuildTex = !cachedScalarsEntry?.oglObject?.getHandle() || cachedScalarsEntry?.hash !== volumeTextureHash;
159
+ const updatedExtents = model.renderable.getUpdatedExtents();
160
+ const hasUpdatedExtents = !!updatedExtents.length;
159
161
  if (reBuildTex) {
160
162
  model.volumeTexture = vtkOpenGLTexture.newInstance();
161
163
  model.volumeTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
@@ -174,6 +176,13 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) {
174
176
  } else {
175
177
  model.volumeTexture = cachedScalarsEntry.oglObject;
176
178
  }
179
+ if (hasUpdatedExtents) {
180
+ // If hasUpdatedExtents, then the texture is partially updated.
181
+ // clear the array to acknowledge the update.
182
+ model.renderable.setUpdatedExtents([]);
183
+ const dims = image.getDimensions();
184
+ model.volumeTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars, false, updatedExtents);
185
+ }
177
186
 
178
187
  // Rebuild the color texture if needed
179
188
  const numComp = scalars.getNumberOfComponents();
@@ -185,15 +185,19 @@ function vtkOpenGLImageResliceMapper(publicAPI, model) {
185
185
  let toString = getImageDataHash(image, scalars);
186
186
  const tex = model._openGLRenderWindow.getGraphicsResourceForObject(scalars);
187
187
  const reBuildTex = !tex?.oglObject?.getHandle() || tex?.hash !== toString;
188
- if (reBuildTex) {
188
+ const updatedExtents = model.renderable.getUpdatedExtents();
189
+ const hasUpdatedExtents = !!updatedExtents.length;
190
+ if (reBuildTex && !hasUpdatedExtents) {
189
191
  model.openGLTexture = vtkOpenGLTexture.newInstance();
190
192
  model.openGLTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
191
193
  // Build the image scalar texture
192
- const dims = image.getDimensions();
193
194
  // Use norm16 for the 3D texture if the extension is available
194
195
  model.openGLTexture.setOglNorm16Ext(model.context.getExtension('EXT_texture_norm16'));
195
196
  model.openGLTexture.resetFormatAndType();
196
- model.openGLTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars);
197
+
198
+ // Build the image scalar texture
199
+ const dims = image.getDimensions();
200
+ model.openGLTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars, false, updatedExtents);
197
201
  model._openGLRenderWindow.setGraphicsResourceForObject(scalars, model.openGLTexture, toString);
198
202
  if (scalars !== model._scalars) {
199
203
  model._openGLRenderWindow.registerGraphicsResourceUser(scalars, publicAPI);
@@ -203,6 +207,13 @@ function vtkOpenGLImageResliceMapper(publicAPI, model) {
203
207
  } else {
204
208
  model.openGLTexture = tex.oglObject;
205
209
  }
210
+ if (hasUpdatedExtents) {
211
+ // If hasUpdatedExtents, then the texture is partially updated.
212
+ // clear the array to acknowledge the update.
213
+ model.renderable.setUpdatedExtents([]);
214
+ const dims = image.getDimensions();
215
+ model.openGLTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars, false, updatedExtents);
216
+ }
206
217
  const ppty = actor.getProperty();
207
218
  const iComps = ppty.getIndependentComponents();
208
219
  const numIComps = iComps ? numComp : 1;
@@ -1,6 +1,6 @@
1
1
  import { Wrap, Filter } from './Texture/Constants';
2
2
  import vtkOpenGLRenderWindow from './RenderWindow';
3
- import { Nullable } from './../../types';
3
+ import { Extent, Nullable } from './../../types';
4
4
  import { VtkDataTypes } from './../../Common/Core/DataArray';
5
5
  import { vtkViewNode } from './../SceneGraph/ViewNode';
6
6
  import { vtkObject, vtkRange } from './../../interfaces';
@@ -275,12 +275,16 @@ export interface vtkOpenGLTexture extends vtkViewNode {
275
275
 
276
276
  /**
277
277
  * Creates a 3D texture from raw data.
278
+ *
279
+ * updatedExtents is currently incompatible with webgl1, since there's no extent scaling.
280
+ *
278
281
  * @param width The width of the texture.
279
282
  * @param height The height of the texture.
280
283
  * @param depth The depth of the texture.
281
284
  * @param numComps The number of components in the texture.
282
285
  * @param dataType The data type of the texture.
283
286
  * @param data The raw data for the texture.
287
+ * @param updatedExtents Only update the specified extents (default: [])
284
288
  * @returns {boolean} True if the texture was successfully created, false otherwise.
285
289
  */
286
290
  create3DFromRaw(
@@ -289,11 +293,15 @@ export interface vtkOpenGLTexture extends vtkViewNode {
289
293
  depth: number,
290
294
  numComps: number,
291
295
  dataType: VtkDataTypes,
292
- data: any
296
+ data: any,
297
+ updatedExtents?: Extent[]
293
298
  ): boolean;
294
299
 
295
300
  /**
296
301
  * Creates a 3D filterable texture from raw data, with a preference for size over accuracy if necessary.
302
+ *
303
+ * updatedExtents is currently incompatible with webgl1, since there's no extent scaling.
304
+ *
297
305
  * @param width The width of the texture.
298
306
  * @param height The height of the texture.
299
307
  * @param depth The depth of the texture.
@@ -302,6 +310,7 @@ export interface vtkOpenGLTexture extends vtkViewNode {
302
310
  * @param values The raw data for the texture.
303
311
  * @param preferSizeOverAccuracy Whether to prefer texture size over accuracy.
304
312
  * @param [ranges] The precomputed ranges of the data (optional). Provided to
313
+ * @param updatedExtents Only update the specified extents (default: [])
305
314
  * prevent computation of the data ranges.
306
315
  * @returns {boolean} True if the texture was successfully created, false
307
316
  * otherwise.
@@ -314,16 +323,21 @@ export interface vtkOpenGLTexture extends vtkViewNode {
314
323
  dataType: VtkDataTypes,
315
324
  values: any,
316
325
  preferSizeOverAccuracy: boolean,
317
- ranges?: vtkRange[]
326
+ ranges?: vtkRange[],
327
+ updatedExtents?: Extent[]
318
328
  ): boolean;
319
329
 
320
330
  /**
321
331
  * Creates a 3D filterable texture from a data array, with a preference for size over accuracy if necessary.
332
+ *
333
+ * updatedExtents is currently incompatible with webgl1, since there's no extent scaling.
334
+ *
322
335
  * @param width The width of the texture.
323
336
  * @param height The height of the texture.
324
337
  * @param depth The depth of the texture.
325
338
  * @param dataArray The data array to use for the texture.
326
339
  * @param preferSizeOverAccuracy Whether to prefer texture size over accuracy.
340
+ * @param updatedExtents Only update the specified extents (default: [])
327
341
  * @returns {boolean} True if the texture was successfully created, false otherwise.
328
342
  */
329
343
  create3DFilterableFromDataArray(
@@ -331,7 +345,8 @@ export interface vtkOpenGLTexture extends vtkViewNode {
331
345
  height: number,
332
346
  depth: number,
333
347
  dataArray: any,
334
- preferSizeOverAccuracy: boolean
348
+ preferSizeOverAccuracy: boolean,
349
+ updatedExtents?: Extent[]
335
350
  ): boolean;
336
351
 
337
352
  /**
@@ -1,3 +1,4 @@
1
+ import DeepEqual from 'fast-deep-equal';
1
2
  import Constants from './Texture/Constants.js';
2
3
  import HalfFloat from '../../Common/Core/HalfFloat.js';
3
4
  import { n as newInstance$1, o as obj, s as set, e as setGet, g as get, i as moveToProtected, a as newTypedArray, c as macro } from '../../macros2.js';
@@ -30,6 +31,16 @@ const {
30
31
  function vtkOpenGLTexture(publicAPI, model) {
31
32
  // Set our className
32
33
  model.classHierarchy.push('vtkOpenGLTexture');
34
+ function getTexParams() {
35
+ return {
36
+ internalFormat: model.internalFormat,
37
+ format: model.format,
38
+ openGLDataType: model.openGLDataType,
39
+ width: model.width,
40
+ height: model.height
41
+ };
42
+ }
43
+
33
44
  // Renders myself
34
45
  publicAPI.render = function () {
35
46
  let renWin = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
@@ -148,6 +159,7 @@ function vtkOpenGLTexture(publicAPI, model) {
148
159
  if (model.context && model.handle) {
149
160
  model.context.deleteTexture(model.handle);
150
161
  }
162
+ model._prevTexParams = null;
151
163
  model.handle = 0;
152
164
  model.numberOfDimensions = 0;
153
165
  model.target = 0;
@@ -209,6 +221,7 @@ function vtkOpenGLTexture(publicAPI, model) {
209
221
  rwin.activateTexture(publicAPI);
210
222
  rwin.deactivateTexture(publicAPI);
211
223
  model.context.deleteTexture(model.handle);
224
+ model._prevTexParams = null;
212
225
  model.handle = 0;
213
226
  model.numberOfDimensions = 0;
214
227
  model.target = 0;
@@ -353,6 +366,7 @@ function vtkOpenGLTexture(publicAPI, model) {
353
366
 
354
367
  //----------------------------------------------------------------------------
355
368
  publicAPI.resetFormatAndType = () => {
369
+ model._prevTexParams = null;
356
370
  model.format = 0;
357
371
  model.internalFormat = 0;
358
372
  model._forceInternalFormat = false;
@@ -499,6 +513,86 @@ function vtkOpenGLTexture(publicAPI, model) {
499
513
  }
500
514
  };
501
515
 
516
+ //----------------------------------------------------------------------------
517
+
518
+ /**
519
+ * Gets the extent's size.
520
+ * @param {Extent} extent
521
+ */
522
+ function getExtentSize(extent) {
523
+ const [xmin, xmax, ymin, ymax, zmin, zmax] = extent;
524
+ return [xmax - xmin + 1, ymax - ymin + 1, zmax - zmin + 1];
525
+ }
526
+
527
+ //----------------------------------------------------------------------------
528
+
529
+ /**
530
+ * Gets the number of pixels in the extent.
531
+ * @param {Extent} extent
532
+ */
533
+ function getExtentPixelCount(extent) {
534
+ const [sx, sy, sz] = getExtentSize(extent);
535
+ return sx * sy * sz;
536
+ }
537
+
538
+ //----------------------------------------------------------------------------
539
+
540
+ /**
541
+ * Reads a flattened extent from the image data and writes to the given output array.
542
+ *
543
+ * Assumes X varies the fastest and Z varies the slowest.
544
+ *
545
+ * @param {*} data
546
+ * @param {*} dataDims
547
+ * @param {Extent} extent
548
+ * @param {TypedArray} outArray
549
+ * @param {number} outOffset
550
+ * @returns
551
+ */
552
+ function readExtentIntoArray(data, dataDims, extent, outArray, outOffset) {
553
+ const [xmin, xmax, ymin, ymax, zmin, zmax] = extent;
554
+ const [dx, dy] = dataDims;
555
+ const sxy = dx * dy;
556
+ let writeOffset = outOffset;
557
+ for (let zi = zmin; zi <= zmax; zi++) {
558
+ const zOffset = zi * sxy;
559
+ for (let yi = ymin; yi <= ymax; yi++) {
560
+ const zyOffset = zOffset + yi * dx;
561
+ // explicit alternative to data.subarray,
562
+ // due to potential perf issues on v8
563
+ for (let readOffset = zyOffset + xmin, end = zyOffset + xmax; readOffset <= end; readOffset++, writeOffset++) {
564
+ outArray[writeOffset] = data[readOffset];
565
+ }
566
+ }
567
+ }
568
+ }
569
+
570
+ //----------------------------------------------------------------------------
571
+
572
+ /**
573
+ * Reads several image extents into a contiguous pixel array.
574
+ *
575
+ * @param {*} data
576
+ * @param {Extent[]} extent
577
+ * @param {TypedArrayConstructor} typedArrayConstructor optional typed array constructor
578
+ * @returns
579
+ */
580
+ function readExtents(data, extents) {
581
+ let typedArrayConstructor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
582
+ const constructor = typedArrayConstructor || data.constructor;
583
+ const numPixels = extents.reduce((count, extent) => count + getExtentPixelCount(extent), 0);
584
+ const extentPixels = new constructor(numPixels);
585
+ const dataDims = [model.width, model.height, model.depth];
586
+ let writeOffset = 0;
587
+ extents.forEach(extent => {
588
+ readExtentIntoArray(data, dataDims, extent, extentPixels, writeOffset);
589
+ writeOffset += getExtentPixelCount(extent);
590
+ });
591
+ return extentPixels;
592
+ }
593
+
594
+ //----------------------------------------------------------------------------
595
+
502
596
  /**
503
597
  * Updates the data array to match the required data type for OpenGL.
504
598
  *
@@ -508,23 +602,30 @@ function vtkOpenGLTexture(publicAPI, model) {
508
602
  * @param {string} dataType - The original data type of the input data.
509
603
  * @param {Array} data - The input data array that needs to be updated.
510
604
  * @param {boolean} [depth=false] - Indicates whether the data is a 3D array.
605
+ * @param {Array<Extent>} imageExtents only consider these image extents (default: [])
511
606
  * @returns {Array} The updated data array that matches the OpenGL data type.
512
607
  */
513
608
  publicAPI.updateArrayDataTypeForGL = function (dataType, data) {
514
609
  let depth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
610
+ let imageExtents = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
515
611
  const pixData = [];
516
612
  let pixCount = model.width * model.height * model.components;
517
613
  if (depth) {
518
614
  pixCount *= model.depth;
519
615
  }
616
+ const onlyUpdateExtents = !!imageExtents.length;
520
617
 
521
618
  // if the opengl data type is float
522
619
  // then the data array must be float
523
620
  if (dataType !== VtkDataTypes.FLOAT && model.openGLDataType === model.context.FLOAT) {
524
621
  for (let idx = 0; idx < data.length; idx++) {
525
622
  if (data[idx]) {
526
- const dataArrayToCopy = data[idx].length > pixCount ? data[idx].subarray(0, pixCount) : data[idx];
527
- pixData.push(new Float32Array(dataArrayToCopy));
623
+ if (onlyUpdateExtents) {
624
+ pixData.push(readExtents(data[idx], imageExtents, Float32Array));
625
+ } else {
626
+ const dataArrayToCopy = data[idx].length > pixCount ? data[idx].subarray(0, pixCount) : data[idx];
627
+ pixData.push(new Float32Array(dataArrayToCopy));
628
+ }
528
629
  } else {
529
630
  pixData.push(null);
530
631
  }
@@ -536,8 +637,12 @@ function vtkOpenGLTexture(publicAPI, model) {
536
637
  if (dataType !== VtkDataTypes.UNSIGNED_CHAR && model.openGLDataType === model.context.UNSIGNED_BYTE) {
537
638
  for (let idx = 0; idx < data.length; idx++) {
538
639
  if (data[idx]) {
539
- const dataArrayToCopy = data[idx].length > pixCount ? data[idx].subarray(0, pixCount) : data[idx];
540
- pixData.push(new Uint8Array(dataArrayToCopy));
640
+ if (onlyUpdateExtents) {
641
+ pixData.push(readExtents(data[idx], imageExtents, Uint8Array));
642
+ } else {
643
+ const dataArrayToCopy = data[idx].length > pixCount ? data[idx].subarray(0, pixCount) : data[idx];
644
+ pixData.push(new Uint8Array(dataArrayToCopy));
645
+ }
541
646
  } else {
542
647
  pixData.push(null);
543
648
  }
@@ -556,9 +661,10 @@ function vtkOpenGLTexture(publicAPI, model) {
556
661
  if (halfFloat) {
557
662
  for (let idx = 0; idx < data.length; idx++) {
558
663
  if (data[idx]) {
559
- const newArray = new Uint16Array(pixCount);
560
- const src = data[idx];
561
- for (let i = 0; i < pixCount; i++) {
664
+ const src = onlyUpdateExtents ? readExtents(data[idx], imageExtents) : data[idx];
665
+ const newArray = new Uint16Array(onlyUpdateExtents ? src.length : pixCount);
666
+ const newArrayLen = newArray.length;
667
+ for (let i = 0; i < newArrayLen; i++) {
562
668
  newArray[i] = toHalf(src[i]);
563
669
  }
564
670
  pixData.push(newArray);
@@ -571,7 +677,7 @@ function vtkOpenGLTexture(publicAPI, model) {
571
677
  // The output has to be filled
572
678
  if (pixData.length === 0) {
573
679
  for (let i = 0; i < data.length; i++) {
574
- pixData.push(data[i]);
680
+ pixData.push(onlyUpdateExtents && data[i] ? readExtents(data[i], imageExtents) : data[i]);
575
681
  }
576
682
  }
577
683
  return pixData;
@@ -1056,7 +1162,8 @@ function vtkOpenGLTexture(publicAPI, model) {
1056
1162
  };
1057
1163
 
1058
1164
  //----------------------------------------------------------------------------
1059
- publicAPI.create3DFromRaw = (width, height, depth, numComps, dataType, data) => {
1165
+ publicAPI.create3DFromRaw = function (width, height, depth, numComps, dataType, data) {
1166
+ let updatedExtents = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : [];
1060
1167
  let dataTypeToUse = dataType;
1061
1168
  let dataToUse = data;
1062
1169
  if (!publicAPI.updateVolumeInfoForGL(dataTypeToUse, numComps) && dataToUse) {
@@ -1098,25 +1205,42 @@ function vtkOpenGLTexture(publicAPI, model) {
1098
1205
  model._openGLRenderWindow.activateTexture(publicAPI);
1099
1206
  publicAPI.createTexture();
1100
1207
  publicAPI.bind();
1208
+ const hasUpdatedExtents = updatedExtents.length > 0;
1209
+
1210
+ // It's possible for the texture parameters to change while
1211
+ // streaming, so check for such a change.
1212
+ const rebuildEntireTexture = !hasUpdatedExtents || !DeepEqual(model._prevTexParams, getTexParams());
1213
+
1101
1214
  // Create an array of texture with one texture
1102
1215
  const dataArray = [dataToUse];
1103
1216
  const is3DArray = true;
1104
- const pixData = publicAPI.updateArrayDataTypeForGL(dataTypeToUse, dataArray, is3DArray);
1217
+ const pixData = publicAPI.updateArrayDataTypeForGL(dataTypeToUse, dataArray, is3DArray, rebuildEntireTexture ? [] : updatedExtents);
1105
1218
  const scaledData = scaleTextureToHighestPowerOfTwo(pixData);
1106
1219
 
1107
1220
  // Source texture data from the PBO.
1108
1221
  // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
1109
1222
  model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
1110
-
1111
- // openGLDataType
1112
-
1113
- if (useTexStorage(dataTypeToUse)) {
1114
- model.context.texStorage3D(model.target, 1, model.internalFormat, model.width, model.height, model.depth);
1115
- if (scaledData[0] != null) {
1116
- model.context.texSubImage3D(model.target, 0, 0, 0, 0, model.width, model.height, model.depth, model.format, model.openGLDataType, scaledData[0]);
1223
+ if (rebuildEntireTexture) {
1224
+ if (useTexStorage(dataTypeToUse)) {
1225
+ model.context.texStorage3D(model.target, 1, model.internalFormat, model.width, model.height, model.depth);
1226
+ if (scaledData[0] != null) {
1227
+ model.context.texSubImage3D(model.target, 0, 0, 0, 0, model.width, model.height, model.depth, model.format, model.openGLDataType, scaledData[0]);
1228
+ }
1229
+ } else {
1230
+ model.context.texImage3D(model.target, 0, model.internalFormat, model.width, model.height, model.depth, 0, model.format, model.openGLDataType, scaledData[0]);
1231
+ }
1232
+ model._prevTexParams = getTexParams();
1233
+ } else if (hasUpdatedExtents) {
1234
+ const extentPixels = scaledData[0];
1235
+ let readOffset = 0;
1236
+ for (let i = 0; i < updatedExtents.length; i++) {
1237
+ const extent = updatedExtents[i];
1238
+ const extentSize = getExtentSize(extent);
1239
+ const extentPixelCount = getExtentPixelCount(extent);
1240
+ const textureData = new extentPixels.constructor(extentPixels.buffer, readOffset, extentPixelCount);
1241
+ readOffset += textureData.byteLength;
1242
+ model.context.texSubImage3D(model.target, 0, extent[0], extent[2], extent[4], extentSize[0], extentSize[1], extentSize[2], model.format, model.openGLDataType, textureData);
1117
1243
  }
1118
- } else {
1119
- model.context.texImage3D(model.target, 0, model.internalFormat, model.width, model.height, model.depth, 0, model.format, model.openGLDataType, scaledData[0]);
1120
1244
  }
1121
1245
  if (model.generateMipmap) {
1122
1246
  model.context.generateMipmap(model.target);
@@ -1132,18 +1256,20 @@ function vtkOpenGLTexture(publicAPI, model) {
1132
1256
  publicAPI.create3DFilterableFromRaw = function (width, height, depth, numberOfComponents, dataType, values) {
1133
1257
  let preferSizeOverAccuracy = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false;
1134
1258
  let ranges = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : undefined;
1259
+ let updatedExtents = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : [];
1135
1260
  return publicAPI.create3DFilterableFromDataArray(width, height, depth, vtkDataArray.newInstance({
1136
1261
  numberOfComponents,
1137
1262
  dataType,
1138
1263
  values,
1139
1264
  ranges
1140
- }), preferSizeOverAccuracy);
1265
+ }), preferSizeOverAccuracy, updatedExtents);
1141
1266
  };
1142
1267
 
1143
1268
  //----------------------------------------------------------------------------
1144
1269
  // This method create a 3D texture from dimensions and a DataArray
1145
1270
  publicAPI.create3DFilterableFromDataArray = function (width, height, depth, dataArray) {
1146
1271
  let preferSizeOverAccuracy = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
1272
+ let updatedExtents = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : [];
1147
1273
  const {
1148
1274
  numComps,
1149
1275
  dataType,
@@ -1178,7 +1304,7 @@ function vtkOpenGLTexture(publicAPI, model) {
1178
1304
 
1179
1305
  // WebGL2 path, we have 3d textures etc
1180
1306
  if (model._openGLRenderWindow.getWebgl2()) {
1181
- return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data);
1307
+ return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data, updatedExtents);
1182
1308
  }
1183
1309
  const numPixelsIn = width * height * depth;
1184
1310
  const scaleOffsetsCopy = structuredClone(scaleOffsets);
@@ -1347,6 +1473,7 @@ function vtkOpenGLTexture(publicAPI, model) {
1347
1473
  const DEFAULT_VALUES = {
1348
1474
  _openGLRenderWindow: null,
1349
1475
  _forceInternalFormat: false,
1476
+ _prevTexParams: null,
1350
1477
  context: null,
1351
1478
  handle: 0,
1352
1479
  sendParametersTime: null,
@@ -1138,7 +1138,9 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1138
1138
  // rebuild the scalarTexture if the data has changed
1139
1139
  toString = getImageDataHash(image, scalars);
1140
1140
  const reBuildTex = !tex?.oglObject?.getHandle() || tex?.hash !== toString;
1141
- if (reBuildTex) {
1141
+ const updatedExtents = model.renderable.getUpdatedExtents();
1142
+ const hasUpdatedExtents = !!updatedExtents.length;
1143
+ if (reBuildTex && !hasUpdatedExtents) {
1142
1144
  model.scalarTexture = vtkOpenGLTexture.newInstance();
1143
1145
  model.scalarTexture.setOpenGLRenderWindow(model._openGLRenderWindow);
1144
1146
  // Build the textures
@@ -1146,7 +1148,7 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1146
1148
  // Use norm16 for scalar texture if the extension is available
1147
1149
  model.scalarTexture.setOglNorm16Ext(model.context.getExtension('EXT_texture_norm16'));
1148
1150
  model.scalarTexture.resetFormatAndType();
1149
- model.scalarTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars, model.renderable.getPreferSizeOverAccuracy());
1151
+ model.scalarTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars);
1150
1152
  if (scalars) {
1151
1153
  model._openGLRenderWindow.setGraphicsResourceForObject(scalars, model.scalarTexture, toString);
1152
1154
  if (scalars !== model._scalars) {
@@ -1158,6 +1160,13 @@ function vtkOpenGLVolumeMapper(publicAPI, model) {
1158
1160
  } else {
1159
1161
  model.scalarTexture = tex.oglObject;
1160
1162
  }
1163
+ if (hasUpdatedExtents) {
1164
+ // If hasUpdatedExtents, then the texture is partially updated.
1165
+ // clear the array to acknowledge the update.
1166
+ model.renderable.setUpdatedExtents([]);
1167
+ const dims = image.getDimensions();
1168
+ model.scalarTexture.create3DFilterableFromDataArray(dims[0], dims[1], dims[2], scalars, false, updatedExtents);
1169
+ }
1161
1170
  if (!model.tris.getCABO().getElementCount()) {
1162
1171
  // build the CABO
1163
1172
  const ptsArray = new Float32Array(12);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "32.11.0",
3
+ "version": "32.12.1",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",
@@ -141,19 +141,20 @@
141
141
  "reformat-only": "prettier --single-quote --trailing-comma es5 --print-width 80 --arrow-parens always --write",
142
142
  "lint-fix": "eslint --fix Sources Examples",
143
143
  "lint": "eslint Sources Examples",
144
- "doc": "kw-doc -c ./Documentation/config.js",
145
- "doc:www": "npm t -- --single-run && kw-doc -c ./Documentation/config.js -s",
146
- "doc:minified": "kw-doc -c ./Documentation/config.js -m",
147
- "doc:generate-api": "node ./Documentation/generate-api-docs.js",
144
+ "doc": "npm run build:pre && kw-doc -c ./Documentation/config.js",
145
+ "doc:www": "npm t -- --single-run && npm run build:pre && kw-doc -c ./Documentation/config.js -s",
146
+ "doc:minified": "npm run build:pre && kw-doc -c ./Documentation/config.js -m",
147
+ "doc:generate-api": "npm run build:pre && node ./Documentation/generate-api-docs.js",
148
148
  "example": "node ./Utilities/ExampleRunner/example-runner-cli.js -c ./Documentation/config.js",
149
149
  "example:https": "node ./Utilities/ExampleRunner/example-runner-cli.js --server-type https -c ./Documentation/config.js",
150
150
  "example:webgpu": "cross-env WEBGPU=1 NO_WEBGL=1 node ./Utilities/ExampleRunner/example-runner-cli.js --server-type https -c ./Documentation/config.js",
151
+ "build:pre": "patch-package",
151
152
  "dev:esm": "npm run build:esm -- -w",
152
- "dev:umd": "webpack --watch --config webpack.dev.js --progress",
153
+ "dev:umd": "npm run build:pre && webpack --watch --config webpack.dev.js --progress",
153
154
  "build": "npm run build:release",
154
- "build:esm": "rollup -c rollup.config.js",
155
- "build:umd": "webpack --config webpack.prod.js --progress",
156
- "build:release": "npm run lint && concurrently \"cross-env NOLINT=1 npm run build:esm\" \"cross-env NOLINT=1 npm run build:umd\"",
155
+ "build:esm": "npm run build:pre && rollup -c rollup.config.js",
156
+ "build:umd": "npm run build:pre && webpack --config webpack.prod.js --progress",
157
+ "build:release": "npm run lint && npm run build:pre && concurrently \"cross-env NOLINT=1 npm run build:esm\" \"cross-env NOLINT=1 npm run build:umd\"",
157
158
  "release:create-packages": "node ./Utilities/ci/build-npm-package.js",
158
159
  "test": "karma start ./karma.conf.js",
159
160
  "test:headless": "karma start ./karma.conf.js --browsers ChromeHeadlessNoSandbox --single-run",
@@ -163,8 +164,7 @@
163
164
  "test:firefox-debug": "karma start ./karma.conf.js --browsers Firefox --no-single-run",
164
165
  "commit": "git cz",
165
166
  "semantic-release": "semantic-release",
166
- "prepare": "node ./Utilities/prepare.js",
167
- "postinstall": "patch-package"
167
+ "prepare": "node ./Utilities/prepare.js"
168
168
  },
169
169
  "config": {
170
170
  "commitizen": {