@kitware/vtk.js 34.0.0-beta.1 → 34.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,65 +1,79 @@
1
1
  import BinaryHelper from '../../Core/BinaryHelper.js';
2
- import { BINARY_HEADER_MAGIC, BINARY_CHUNK_TYPES, BINARY_CHUNK_HEADER_INTS, BINARY_HEADER_LENGTH, BINARY_HEADER_INTS } from './Constants.js';
2
+ import { BINARY_HEADER_LENGTH, BINARY_HEADER_MAGIC, BINARY_CHUNK_TYPES, BINARY_CHUNK_HEADER_INTS, BINARY_HEADER_INTS } from './Constants.js';
3
3
 
4
4
  function getChunkInfo(headerStart, data) {
5
+ // Cache array view
5
6
  const header = new Uint32Array(data, headerStart, BINARY_CHUNK_HEADER_INTS);
6
7
  const chunkStart = headerStart + BINARY_CHUNK_HEADER_INTS * 4;
7
- const chunkLength = header[0];
8
- const chunkType = header[1];
9
8
  return {
10
9
  start: chunkStart,
11
- length: chunkLength,
12
- type: chunkType
10
+ length: header[0],
11
+ type: header[1]
13
12
  };
14
13
  }
15
14
  function getAllChunkInfos(data) {
16
- const infos = [];
15
+ // Pre-calculate array size
16
+ const maxChunks = Math.floor((data.byteLength - BINARY_HEADER_LENGTH) / 8);
17
+ const infos = new Array(maxChunks);
17
18
  let chunkStart = BINARY_HEADER_INTS * 4;
19
+ let chunkCount = 0;
18
20
  while (chunkStart < data.byteLength) {
19
21
  const chunkInfo = getChunkInfo(chunkStart, data);
20
- infos.push(chunkInfo);
22
+ infos[chunkCount++] = chunkInfo;
21
23
  chunkStart += chunkInfo.length + BINARY_CHUNK_HEADER_INTS * 4;
22
24
  }
23
- return infos;
25
+
26
+ // Trim array to actual size
27
+ return infos.slice(0, chunkCount);
24
28
  }
25
29
  function getJsonFromChunk(chunkInfo, data) {
26
- const chunkLength = chunkInfo.length;
27
30
  const jsonStart = (BINARY_HEADER_INTS + BINARY_CHUNK_HEADER_INTS) * 4;
28
- const jsonSlice = new Uint8Array(data, jsonStart, chunkLength);
29
- const stringBuffer = BinaryHelper.arrayBufferToString(jsonSlice);
30
- return JSON.parse(stringBuffer);
31
+ const jsonView = new Uint8Array(data, jsonStart, chunkInfo.length);
32
+ const decoder = new TextDecoder('utf-8');
33
+ const jsonString = decoder.decode(jsonView);
34
+ return JSON.parse(jsonString);
31
35
  }
32
36
  function getBufferFromChunk(chunkInfo, data) {
33
37
  return data.slice(chunkInfo.start, chunkInfo.start + chunkInfo.length);
34
38
  }
35
39
  function parseGLB(data) {
36
- let json;
37
- const buffers = [];
40
+ if (data.byteLength < BINARY_HEADER_LENGTH) {
41
+ throw new Error('Invalid GLB: File too small');
42
+ }
43
+
44
+ // Cache DataView
38
45
  const headerView = new DataView(data, 0, BINARY_HEADER_LENGTH);
46
+ const magic = new Uint8Array(data, 0, 4);
39
47
  const header = {
40
- magic: BinaryHelper.arrayBufferToString(new Uint8Array(data, 0, 4)),
48
+ magic: BinaryHelper.arrayBufferToString(magic),
41
49
  version: headerView.getUint32(4, true),
42
50
  length: headerView.getUint32(8, true)
43
51
  };
44
52
  if (header.magic !== BINARY_HEADER_MAGIC) {
45
53
  throw new Error('Unsupported glTF-Binary header.');
46
- } else if (header.version < 2.0) {
54
+ }
55
+ if (header.version < 2.0) {
47
56
  throw new Error('Unsupported legacy binary file detected.');
48
57
  }
58
+ if (header.length > data.byteLength) {
59
+ throw new Error('Invalid GLB: Declared length exceeds file size');
60
+ }
49
61
  const chunkInfos = getAllChunkInfos(data);
50
- chunkInfos.forEach(chunkInfo => {
62
+ let json = null;
63
+ const buffers = [];
64
+
65
+ // Process chunks sequentially
66
+ for (let i = 0; i < chunkInfos.length; i++) {
67
+ const chunkInfo = chunkInfos[i];
51
68
  if (chunkInfo.type === BINARY_CHUNK_TYPES.JSON && !json) {
52
69
  json = getJsonFromChunk(chunkInfo, data);
53
70
  } else if (chunkInfo.type === BINARY_CHUNK_TYPES.BIN) {
54
71
  buffers.push(getBufferFromChunk(chunkInfo, data));
55
72
  }
56
- });
73
+ }
57
74
  if (!json) {
58
75
  throw new Error('glTF-Binary: JSON content not found.');
59
76
  }
60
- if (!buffers) {
61
- throw new Error('glTF-Binary: Binary chunk not found.');
62
- }
63
77
  return {
64
78
  json,
65
79
  buffers
@@ -210,15 +210,24 @@ class GLTFParser {
210
210
  if (accessor.bufferView) {
211
211
  const buffer = accessor.bufferView.buffer;
212
212
  const {
213
- ArrayType,
214
- byteLength
213
+ ArrayType
215
214
  } = getAccessorArrayTypeAndLength(accessor, accessor.bufferView);
216
- const byteOffset = (accessor.bufferView.byteOffset || 0) + (accessor.byteOffset || 0) + buffer.byteOffset;
217
- let slicedBufffer = buffer.arrayBuffer.slice(byteOffset, byteOffset + byteLength);
215
+ const baseByteOffset = (accessor.bufferView.byteOffset || 0) + buffer.byteOffset;
216
+ const byteOffset = baseByteOffset + (accessor.byteOffset || 0);
217
+ let arrayBufferView;
218
218
  if (accessor.bufferView.byteStride) {
219
- slicedBufffer = this.getValueFromInterleavedBuffer(buffer, byteOffset, accessor.bufferView.byteStride, accessor.bytesPerElement, accessor.count);
219
+ // Only extract if stride is not equal to element size
220
+ if (accessor.bufferView.byteStride === accessor.bytesPerElement) {
221
+ arrayBufferView = new ArrayType(buffer.arrayBuffer, byteOffset, accessor.count * accessor.components);
222
+ } else {
223
+ // Interleaved buffer, extract only needed bytes
224
+ const interleavedBuffer = this.getValueFromInterleavedBuffer(buffer, byteOffset, accessor.bufferView.byteStride, accessor.bytesPerElement, accessor.count);
225
+ arrayBufferView = new ArrayType(interleavedBuffer);
226
+ }
227
+ } else {
228
+ arrayBufferView = new ArrayType(buffer.arrayBuffer, byteOffset, accessor.count * accessor.components);
220
229
  }
221
- accessor.value = new ArrayType(slicedBufffer);
230
+ accessor.value = arrayBufferView;
222
231
  }
223
232
  return accessor;
224
233
  }
@@ -149,6 +149,7 @@ async function createPolyDataFromGLTFMesh(primitive) {
149
149
  polyData.setStrips(cells);
150
150
  break;
151
151
  default:
152
+ cells.delete();
152
153
  vtkWarningMacro('Invalid primitive draw mode. Ignoring connectivity.');
153
154
  }
154
155
  return polyData;
@@ -198,15 +199,7 @@ async function createPropertyFromGLTFMaterial(model, material, actor) {
198
199
  const sampler = tex.sampler;
199
200
  const image = await loadImage(tex.source);
200
201
  const diffuseTex = createVTKTextureFromGLTFTexture(image, sampler);
201
-
202
- // FIXME: Workaround for textures not showing up in WebGL
203
- const viewAPI = model.renderer.getRenderWindow();
204
- const isWebGL = viewAPI.getViews()[0].isA('vtkOpenGLRenderWindow');
205
- if (isWebGL) {
206
- actor.addTexture(diffuseTex);
207
- } else {
208
- property.setDiffuseTexture(diffuseTex);
209
- }
202
+ property.setDiffuseTexture(diffuseTex);
210
203
  }
211
204
 
212
205
  // Handle metallic-roughness texture (metallicRoughnessTexture)
@@ -224,7 +217,7 @@ async function createPropertyFromGLTFMaterial(model, material, actor) {
224
217
  material.occlusionTexture.extensions;
225
218
  const tex = material.occlusionTexture.texture;
226
219
  const sampler = tex.sampler;
227
- const aoImage = await loadImage(tex.source, 'r');
220
+ const aoImage = await loadImage(tex.source);
228
221
  const aoTex = createVTKTextureFromGLTFTexture(aoImage, sampler);
229
222
  property.setAmbientOcclusionTexture(aoTex);
230
223
  }
@@ -299,6 +292,8 @@ function handlePrimitiveExtensions(nodeId, extensions, model) {
299
292
  case 'KHR_materials_variants':
300
293
  model.variantMappings.set(nodeId, extension.mappings);
301
294
  break;
295
+ case 'KHR_draco_mesh_compression':
296
+ break;
302
297
  default:
303
298
  vtkWarningMacro(`Unhandled extension: ${extensionName}`);
304
299
  }
@@ -314,6 +309,7 @@ async function createActorFromGTLFNode(worldMatrix) {
314
309
  const actor = vtkActor.newInstance();
315
310
  const mapper = vtkMapper.newInstance();
316
311
  mapper.setColorModeToDirectScalars();
312
+ mapper.setInterpolateScalarsBeforeMapping(true);
317
313
  actor.setMapper(mapper);
318
314
  actor.setUserMatrix(worldMatrix);
319
315
  const polydata = vtkPolyData.newInstance();
@@ -330,6 +326,7 @@ async function createActorFromGTLFPrimitive(model, primitive, worldMatrix) {
330
326
  const actor = vtkActor.newInstance();
331
327
  const mapper = vtkMapper.newInstance();
332
328
  mapper.setColorModeToDirectScalars();
329
+ mapper.setInterpolateScalarsBeforeMapping(true);
333
330
  actor.setMapper(mapper);
334
331
  actor.setUserMatrix(worldMatrix);
335
332
  const polydata = await createPolyDataFromGLTFMesh(primitive);
@@ -1,7 +1,5 @@
1
- import WebworkerPromise from 'webworker-promise';
2
1
  import { m as macro } from '../../../macros2.js';
3
2
  import vtkTexture from '../../../Rendering/Core/Texture.js';
4
- import { W as WorkerFactory } from '../../../_virtual/rollup-plugin-worker-loader__module_Sources/IO/Geometry/GLTFImporter/ORMTexture.worker.js';
5
3
  import { ARRAY_TYPES, COMPONENTS, BYTES, GL_SAMPLER } from './Constants.js';
6
4
 
7
5
  const {
@@ -83,33 +81,18 @@ function resolveUrl(url, originalPath) {
83
81
  /**
84
82
  * Loads image from buffer or URI
85
83
  * @param {*} image
86
- * @param {*} channel
87
84
  * @returns
88
85
  */
89
- async function loadImage(image, channel) {
90
- let forceReLoad = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
91
- // Initialize cache if it doesn't exist
92
- if (!image.cache) {
93
- image.cache = {};
94
- }
95
-
96
- // Return cached result for the channel if available and not forced to reload
97
- if (!forceReLoad && image.cache[channel]) {
98
- return image.cache[channel];
99
- }
100
- const worker = new WebworkerPromise(new WorkerFactory());
86
+ async function loadImage(image) {
101
87
  if (image.bufferView) {
102
- return worker.postMessage({
103
- imageBuffer: image.bufferView.data,
104
- mimeType: image.mimeType,
105
- channel
106
- }).then(result => {
107
- // Cache the bitmap based on the channel
108
- image.cache[channel] = result.bitmap;
109
- return result.bitmap;
110
- }).finally(() => {
111
- worker.terminate();
88
+ const blob = new Blob([image.bufferView.data], {
89
+ type: image.mimeType
90
+ });
91
+ const bitmap = await createImageBitmap(blob, {
92
+ colorSpaceConversion: 'none',
93
+ imageOrientation: 'flipY'
112
94
  });
95
+ return bitmap;
113
96
  }
114
97
  if (image.uri) {
115
98
  vtkWarningMacro('Falling back to image uri', image.uri);
@@ -117,7 +100,6 @@ async function loadImage(image, channel) {
117
100
  const img = new Image();
118
101
  img.crossOrigin = 'Anonymous';
119
102
  img.onload = () => {
120
- image.cache[channel] = img; // Cache the loaded image based on the channel
121
103
  resolve(img);
122
104
  };
123
105
  img.onerror = reject;
@@ -158,7 +140,7 @@ function createVTKTextureFromGLTFTexture(image, sampler, extensions) {
158
140
  texture.setEdgeClamp(true);
159
141
  }
160
142
  }
161
- texture.setJsImageData(image);
143
+ texture.setImageBitmap(image);
162
144
  return texture;
163
145
  }
164
146
 
@@ -1,4 +1,5 @@
1
1
  import { vtkAlgorithm } from './../../interfaces';
2
+ import { Nullable } from './../../types';
2
3
 
3
4
  /**
4
5
  *
@@ -15,67 +16,110 @@ export interface ITextureInitialValues {
15
16
 
16
17
  export interface vtkTexture extends vtkAlgorithm {
17
18
  /**
18
- *
19
+ * Returns the canvas used by the texture.
20
+ */
21
+ getCanvas(): Nullable<HTMLCanvasElement>;
22
+
23
+ /**
24
+ * Returns true if the texture is set to repeat at the edges.
19
25
  */
20
26
  getRepeat(): boolean;
21
27
 
22
28
  /**
23
- *
29
+ * Returns true if the texture is set to clamp at the edges.
24
30
  */
25
31
  getEdgeClamp(): boolean;
26
32
 
27
33
  /**
28
- *
34
+ * Returns true if the texture is set to interpolate between texels.
29
35
  */
30
36
  getInterpolate(): boolean;
31
37
 
32
38
  /**
33
- *
39
+ * Returns the image used by the texture.
40
+ */
41
+ getImage(): Nullable<HTMLImageElement>;
42
+
43
+ /**
44
+ * Returns an ImageBitmap object.
34
45
  */
35
- getImage(): any;
46
+ getImageBitmap(): Nullable<ImageBitmap>;
36
47
 
37
48
  /**
38
- *
49
+ * Returns true if the image is loaded.
39
50
  */
40
51
  getImageLoaded(): boolean;
41
52
 
42
53
  /**
43
- *
54
+ * Returns the input image data object.
55
+ */
56
+ getInputAsJsImageData(): Nullable<
57
+ ImageData | ImageBitmap | HTMLCanvasElement | HTMLImageElement
58
+ >;
59
+
60
+ /**
61
+ * Returns the current mip level of the texture.
44
62
  */
45
63
  getMipLevel(): number;
46
64
 
47
65
  /**
48
- *
49
- * @param repeat
50
- * @default false
66
+ * Returns true if the texture can be resized at run time.
67
+ * This is useful for dynamic textures that may change size based on user
68
+ * interaction or other factors.
51
69
  */
52
- setRepeat(repeat: boolean): boolean;
70
+ getResizable(): boolean;
71
+
72
+ /**
73
+ * Returns the canvas used by the texture.
74
+ */
75
+ setCanvas(canvas: HTMLCanvasElement): void;
53
76
 
54
77
  /**
55
- *
78
+ * Sets the texture to clamp at the edges.
56
79
  * @param edgeClamp
57
80
  * @default false
58
81
  */
59
82
  setEdgeClamp(edgeClamp: boolean): boolean;
60
83
 
61
84
  /**
62
- *
85
+ * Sets the texture to interpolate between texels.
63
86
  * @param interpolate
64
87
  * @default false
65
88
  */
66
89
  setInterpolate(interpolate: boolean): boolean;
67
90
 
68
91
  /**
69
- *
92
+ * Sets the image used by the texture.
70
93
  * @param image
71
94
  * @default null
72
95
  */
73
- setImage(image: any): void;
96
+ setImage(image: HTMLImageElement): void;
74
97
 
75
98
  /**
99
+ * Sets the image as an ImageBitmap object.
100
+ * Supported in WebGPU only.
101
+ * @param imageBitmap
102
+ */
103
+ setImageBitmap(imageBitmap: ImageBitmap): void;
104
+
105
+ /**
106
+ * Sets the input image data as a JavaScript ImageData object.
107
+ * @param imageData
108
+ */
109
+ setJsImageData(imageData: ImageData): void;
110
+
111
+ /**
112
+ * Sets the current mip level of the texture.
76
113
  * @param level
77
114
  */
78
115
  setMipLevel(level: number): boolean;
116
+
117
+ /**
118
+ * Sets the texture to repeat at the edges.
119
+ * @param repeat
120
+ * @default false
121
+ */
122
+ setRepeat(repeat: boolean): boolean;
79
123
  }
80
124
 
81
125
  /**
@@ -98,26 +142,34 @@ export function extend(
98
142
  export function newInstance(initialValues?: ITextureInitialValues): vtkTexture;
99
143
 
100
144
  /**
101
- * Method used to create mipmaps from given texture data. Works best with textures that have a
102
- * width and a height that are powers of two.
145
+ * Generates mipmaps for a given GPU texture using a compute shader.
146
+ *
147
+ * This function iteratively generates each mip level for the provided texture,
148
+ * using a bilinear downsampling compute shader implemented in WGSL. It creates
149
+ * the necessary pipeline, bind groups, and dispatches compute passes for each
150
+ * mip level.
103
151
  *
104
- * @param nativeArray the array of data to create mipmaps from.
105
- * @param width the width of the data
106
- * @param height the height of the data
107
- * @param level the level to which additional mipmaps are generated.
152
+ * @param {GPUDevice} device - The WebGPU device used to create resources and submit commands.
153
+ * @param {GPUTexture} texture - The GPU texture for which mipmaps will be generated.
154
+ * @param {number} mipLevelCount - The total number of mip levels to generate (including the base level).
108
155
  */
109
156
  export function generateMipmaps(
110
- nativeArray: any,
111
- width: number,
112
- height: number,
113
- level: number
157
+ device: any,
158
+ texture: any,
159
+ mipLevelCount: number
114
160
  ): Array<Uint8ClampedArray>;
115
161
 
116
162
  /**
117
- * vtkTexture is an image algorithm that handles loading and binding of texture maps.
118
- * It obtains its data from an input image data dataset type.
119
- * Thus you can create visualization pipelines to read, process, and construct textures.
120
- * Note that textures will only work if texture coordinates are also defined, and if the rendering system supports texture.
163
+ * vtkTexture is an image algorithm that handles loading and binding of texture
164
+ * maps. It obtains its data from an input image data dataset type. Thus you can
165
+ * create visualization pipelines to read, process, and construct textures. Note
166
+ * that textures will only work if texture coordinates are also defined, and if
167
+ * the rendering system supports texture.
168
+ *
169
+ * This class is used in both WebGL and WebGPU rendering backends, but the
170
+ * implementation details may vary. In WebGL, it uses HTMLImageElement and
171
+ * HTMLCanvasElement for textures, while in WebGPU, it uses HTMLImageElement,
172
+ * HTMLCanvasElement, and ImageBitmap.
121
173
  */
122
174
  export declare const vtkTexture: {
123
175
  newInstance: typeof newInstance;