@kitware/vtk.js 32.6.1 → 32.7.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.
@@ -0,0 +1,507 @@
1
+ import { m as macro } from '../../../macros2.js';
2
+ import { A as degreesFromRadians } from '../../../Common/Core/Math/index.js';
3
+ import vtkActor from '../../../Rendering/Core/Actor.js';
4
+ import vtkCamera from '../../../Rendering/Core/Camera.js';
5
+ import vtkDataArray from '../../../Common/Core/DataArray.js';
6
+ import vtkPolyData from '../../../Common/DataModel/PolyData.js';
7
+ import vtkMapper from '../../../Rendering/Core/Mapper.js';
8
+ import vtkCellArray from '../../../Common/Core/CellArray.js';
9
+ import vtkTransform from '../../../Common/Transform/Transform.js';
10
+ import GLTFParser from './Parser.js';
11
+ import { ALPHA_MODE, SEMANTIC_ATTRIBUTE_MAP, MODES } from './Constants.js';
12
+ import { loadImage, createVTKTextureFromGLTFTexture } from './Utils.js';
13
+ import { handleKHRMaterialsSpecular, handleKHRMaterialsIor, handleKHRMaterialsUnlit, handleKHRMaterialsVariants, handleKHRLightsPunctual, handleKHRDracoMeshCompression } from './Extensions.js';
14
+ import { mat4, vec3, quat } from 'gl-matrix';
15
+
16
+ const {
17
+ vtkWarningMacro,
18
+ vtkDebugMacro
19
+ } = macro;
20
+
21
+ /**
22
+ * Parses a GLTF objects
23
+ * @param {Object} gltf - The GLTF object to parse
24
+ * @returns {glTF} The parsed GLTF object
25
+ */
26
+ async function parseGLTF(gltf, options) {
27
+ const parser = new GLTFParser(gltf, options);
28
+ const tree = await parser.parse();
29
+ return tree;
30
+ }
31
+
32
+ /**
33
+ * Creates VTK polydata from a GLTF mesh
34
+ * @param {GLTFMesh} mesh - The GLTF mesh
35
+ * @returns {vtkPolyData} The created VTK polydata
36
+ */
37
+ async function createPolyDataFromGLTFMesh(mesh) {
38
+ const primitive = mesh.primitives[0]; // For simplicity, we'll just use the first primitive
39
+
40
+ if (!primitive || !primitive.attributes) {
41
+ vtkWarningMacro('Mesh has no position data, skipping');
42
+ return null;
43
+ }
44
+ const mode = primitive.mode;
45
+ if (primitive.extensions?.KHR_draco_mesh_compression) {
46
+ return handleKHRDracoMeshCompression(primitive.extensions.KHR_draco_mesh_compression);
47
+ }
48
+ const polyData = vtkPolyData.newInstance();
49
+ const cells = vtkCellArray.newInstance();
50
+ const pointData = polyData.getPointData();
51
+ const attrs = Object.entries(primitive.attributes);
52
+ attrs.forEach(async _ref => {
53
+ let [attributeName, accessor] = _ref;
54
+ switch (attributeName) {
55
+ case SEMANTIC_ATTRIBUTE_MAP.POSITION:
56
+ {
57
+ const position = primitive.attributes.position.value;
58
+ polyData.getPoints().setData(position, primitive.attributes.position.component);
59
+ break;
60
+ }
61
+ case SEMANTIC_ATTRIBUTE_MAP.NORMAL:
62
+ {
63
+ const normals = primitive.attributes.normal.value;
64
+ pointData.setNormals(vtkDataArray.newInstance({
65
+ name: 'Normals',
66
+ values: normals,
67
+ numberOfComponents: primitive.attributes.normal.components
68
+ }));
69
+ break;
70
+ }
71
+ case SEMANTIC_ATTRIBUTE_MAP.COLOR_0:
72
+ {
73
+ const color = primitive.attributes.color.value;
74
+ pointData.setScalars(vtkDataArray.newInstance({
75
+ name: 'Scalars',
76
+ values: color,
77
+ numberOfComponents: primitive.attributes.color.components
78
+ }));
79
+ break;
80
+ }
81
+ case SEMANTIC_ATTRIBUTE_MAP.TEXCOORD_0:
82
+ {
83
+ const tcoords0 = primitive.attributes.texcoord0.value;
84
+ const da = vtkDataArray.newInstance({
85
+ name: 'TEXCOORD_0',
86
+ values: tcoords0,
87
+ numberOfComponents: primitive.attributes.texcoord0.components
88
+ });
89
+ pointData.addArray(da);
90
+ pointData.setActiveTCoords(da.getName());
91
+ break;
92
+ }
93
+ case SEMANTIC_ATTRIBUTE_MAP.TEXCOORD_1:
94
+ {
95
+ const tcoords = primitive.attributes.texcoord1.value;
96
+ const dac = vtkDataArray.newInstance({
97
+ name: 'TEXCOORD_1',
98
+ values: tcoords,
99
+ numberOfComponents: primitive.attributes.texcoord1.components
100
+ });
101
+ pointData.addArray(dac);
102
+ break;
103
+ }
104
+ case SEMANTIC_ATTRIBUTE_MAP.TANGENT:
105
+ {
106
+ const tangent = primitive.attributes.tangent.value;
107
+ const dat = vtkDataArray.newInstance({
108
+ name: 'Tangents',
109
+ values: tangent,
110
+ numberOfComponents: primitive.attributes.tangent.components
111
+ });
112
+ pointData.addArray(dat);
113
+ break;
114
+ }
115
+ default:
116
+ vtkWarningMacro(`Unhandled attribute: ${attributeName}`);
117
+ }
118
+ });
119
+
120
+ // Handle indices if available
121
+ if (primitive.indices !== undefined) {
122
+ const indices = primitive.indices.value;
123
+ const nCells = indices.length - 2;
124
+ switch (mode) {
125
+ case MODES.GL_LINE_STRIP:
126
+ case MODES.GL_TRIANGLE_STRIP:
127
+ case MODES.GL_LINE_LOOP:
128
+ vtkWarningMacro('GL_LINE_LOOP not implemented');
129
+ break;
130
+ default:
131
+ cells.resize(4 * indices.length / 3);
132
+ for (let cellId = 0; cellId < nCells; cellId += 3) {
133
+ const cell = indices.slice(cellId, cellId + 3);
134
+ cells.insertNextCell(cell);
135
+ }
136
+ }
137
+ }
138
+ switch (mode) {
139
+ case MODES.GL_TRIANGLES:
140
+ case MODES.GL_TRIANGLE_FAN:
141
+ polyData.setPolys(cells);
142
+ break;
143
+ case MODES.GL_LINES:
144
+ case MODES.GL_LINE_STRIP:
145
+ case MODES.GL_LINE_LOOP:
146
+ polyData.setLines(cells);
147
+ break;
148
+ case MODES.GL_POINTS:
149
+ polyData.setVerts(cells);
150
+ break;
151
+ case MODES.GL_TRIANGLE_STRIP:
152
+ polyData.setStrips(cells);
153
+ break;
154
+ default:
155
+ vtkWarningMacro('Invalid primitive draw mode. Ignoring connectivity.');
156
+ }
157
+ return polyData;
158
+ }
159
+
160
+ /**
161
+ * Creates a VTK property from a GLTF material
162
+ * @param {*} model - The vtk model object
163
+ * @param {GLTFMaterial} material - The GLTF material
164
+ * @param {vtkActor} actor - The VTK actor
165
+ */
166
+ async function createPropertyFromGLTFMaterial(model, material, actor) {
167
+ let metallicFactor = 1.0;
168
+ let roughnessFactor = 1.0;
169
+ const emissiveFactor = material.emissiveFactor;
170
+ const property = actor.getProperty();
171
+ const pbr = material.pbrMetallicRoughness;
172
+ if (pbr !== undefined) {
173
+ if (!pbr?.metallicFactor || pbr?.metallicFactor <= 0 || pbr?.metallicFactor >= 1) {
174
+ vtkWarningMacro('Invalid material.pbrMetallicRoughness.metallicFactor value. Using default value instead.');
175
+ } else metallicFactor = pbr.metallicFactor;
176
+ if (!pbr?.roughnessFactor || pbr?.roughnessFactor <= 0 || pbr?.roughnessFactor >= 1) {
177
+ vtkWarningMacro('Invalid material.pbrMetallicRoughness.roughnessFactor value. Using default value instead.');
178
+ } else roughnessFactor = pbr.roughnessFactor;
179
+ const color = pbr.baseColorFactor;
180
+ if (color !== undefined) {
181
+ property.setDiffuseColor(color[0], color[1], color[2]);
182
+ property.setOpacity(color[3]);
183
+ }
184
+ property.setMetallic(metallicFactor);
185
+ property.setRoughness(roughnessFactor);
186
+ property.setEmission(emissiveFactor);
187
+ if (pbr.baseColorTexture) {
188
+ pbr.baseColorTexture.extensions;
189
+ const tex = pbr.baseColorTexture.texture;
190
+ if (tex.extensions !== undefined) {
191
+ const extensionsNames = Object.keys(tex.extensions);
192
+ extensionsNames.forEach(extensionName => {
193
+ // TODO: Handle KHR_texture_basisu extension
194
+ // const extension = tex.extensions[extensionName];
195
+ switch (extensionName) {
196
+ default:
197
+ vtkWarningMacro(`Unhandled extension: ${extensionName}`);
198
+ }
199
+ });
200
+ }
201
+ const sampler = tex.sampler;
202
+ const image = await loadImage(tex.source);
203
+ const diffuseTex = createVTKTextureFromGLTFTexture(image, sampler);
204
+
205
+ // FIXME: Workaround for textures not showing up in WebGL
206
+ const viewAPI = model.renderer.getRenderWindow();
207
+ const isWebGL = viewAPI.getViews()[0].isA('vtkOpenGLRenderWindow');
208
+ if (isWebGL) {
209
+ actor.addTexture(diffuseTex);
210
+ } else {
211
+ property.setDiffuseTexture(diffuseTex);
212
+ }
213
+ }
214
+ if (pbr.metallicRoughnessTexture) {
215
+ pbr.metallicRoughnessTexture.extensions;
216
+ const tex = pbr.metallicRoughnessTexture.texture;
217
+ const sampler = tex.sampler;
218
+ const metallicImage = await loadImage(tex.source, 'b');
219
+ const metallicTex = createVTKTextureFromGLTFTexture(metallicImage, sampler);
220
+ property.setMetallicTexture(metallicTex);
221
+ const roughnessImage = await loadImage(tex.source, 'g');
222
+ const roughnessTex = createVTKTextureFromGLTFTexture(roughnessImage, sampler);
223
+ property.setRoughnessTexture(roughnessTex);
224
+ }
225
+
226
+ // Handle ambient occlusion texture (occlusionTexture)
227
+ if (material.occlusionTexture) {
228
+ material.occlusionTexture.extensions;
229
+ const tex = material.occlusionTexture.texture;
230
+ const sampler = tex.sampler;
231
+ const aoImage = await loadImage(tex.source, 'r');
232
+ const aoTex = createVTKTextureFromGLTFTexture(aoImage, sampler);
233
+ property.setAmbientOcclusionTexture(aoTex);
234
+ }
235
+
236
+ // Handle emissive texture (emissiveTexture)
237
+ if (material.emissiveTexture) {
238
+ material.emissiveTexture.extensions;
239
+ const tex = material.emissiveTexture.texture;
240
+ const sampler = tex.sampler;
241
+ const emissiveImage = await loadImage(tex.source);
242
+ const emissiveTex = createVTKTextureFromGLTFTexture(emissiveImage, sampler);
243
+ property.setEmissionTexture(emissiveTex);
244
+
245
+ // Handle mutiple Uvs
246
+ if (material.emissiveTexture.texCoord !== undefined) {
247
+ const pd = actor.getMapper().getInputData().getPointData();
248
+ pd.setActiveTCoords(`TEXCOORD_${material.emissiveTexture.texCoord}`);
249
+ }
250
+ }
251
+
252
+ // Handle normal texture (normalTexture)
253
+ if (material.normalTexture) {
254
+ material.normalTexture.extensions;
255
+ const tex = material.normalTexture.texture;
256
+ const sampler = tex.sampler;
257
+ const normalImage = await loadImage(tex.source);
258
+ const normalTex = createVTKTextureFromGLTFTexture(normalImage, sampler);
259
+ property.setNormalTexture(normalTex);
260
+ if (material.normalTexture.scale !== undefined) {
261
+ property.setNormalStrength(material.normalTexture.scale);
262
+ }
263
+ }
264
+ }
265
+
266
+ // Material extensions
267
+ if (material.extensions !== undefined) {
268
+ const extensionsNames = Object.keys(material.extensions);
269
+ extensionsNames.forEach(extensionName => {
270
+ const extension = material.extensions[extensionName];
271
+ switch (extensionName) {
272
+ case 'KHR_materials_unlit':
273
+ handleKHRMaterialsUnlit(extension, property);
274
+ break;
275
+ case 'KHR_materials_ior':
276
+ handleKHRMaterialsIor(extension, property);
277
+ break;
278
+ case 'KHR_materials_specular':
279
+ handleKHRMaterialsSpecular(extension, property);
280
+ break;
281
+ default:
282
+ vtkWarningMacro(`Unhandled extension: ${extensionName}`);
283
+ }
284
+ });
285
+ }
286
+ if (material.alphaMode !== ALPHA_MODE.OPAQUE) {
287
+ actor.setForceTranslucent(true);
288
+ }
289
+ property.setBackfaceCulling(!material.doubleSided);
290
+ }
291
+
292
+ /**
293
+ * Handles primitive extensions
294
+ * @param {*} extensions The extensions object
295
+ * @param {*} model The vtk model object
296
+ * @param {GLTFNode} node The GLTF node
297
+ */
298
+ function handlePrimitiveExtensions(extensions, model, node) {
299
+ const extensionsNames = Object.keys(extensions);
300
+ extensionsNames.forEach(extensionName => {
301
+ const extension = extensions[extensionName];
302
+ switch (extensionName) {
303
+ case 'KHR_materials_variants':
304
+ model.variantMappings.set(node.id, extension.mappings);
305
+ break;
306
+ default:
307
+ vtkWarningMacro(`Unhandled extension: ${extensionName}`);
308
+ }
309
+ });
310
+ }
311
+
312
+ /**
313
+ * Creates a VTK actor from a GLTF mesh
314
+ * @param {GLTFMesh} mesh - The GLTF mesh
315
+ * @returns {vtkActor} The created VTK actor
316
+ */
317
+ async function createActorFromGTLFNode(model, node, worldMatrix) {
318
+ const actor = vtkActor.newInstance();
319
+ const mapper = vtkMapper.newInstance();
320
+ mapper.setColorModeToDirectScalars();
321
+ actor.setMapper(mapper);
322
+ actor.setUserMatrix(worldMatrix);
323
+ if (node.mesh !== undefined) {
324
+ const polyData = await createPolyDataFromGLTFMesh(node.mesh);
325
+ mapper.setInputData(polyData);
326
+ const primitive = node.mesh.primitives[0]; // the first one for now
327
+
328
+ // Support for materials
329
+ if (primitive.material !== undefined) {
330
+ await createPropertyFromGLTFMaterial(model, primitive.material, actor);
331
+ }
332
+ if (primitive.extensions !== undefined) {
333
+ handlePrimitiveExtensions(primitive.extensions, model, node);
334
+ }
335
+ } else {
336
+ const polyData = vtkPolyData.newInstance();
337
+ mapper.setInputData(polyData);
338
+ }
339
+ return actor;
340
+ }
341
+
342
+ /**
343
+ *
344
+ * @param {GLTFAnimation} animation
345
+ * @returns
346
+ */
347
+ function createGLTFAnimation(animation) {
348
+ vtkDebugMacro('Creating animation:', animation);
349
+ return {
350
+ name: animation.name,
351
+ channels: animation.channels,
352
+ samplers: animation.samplers,
353
+ getChannelByTargetNode(nodeIndex) {
354
+ return this.channels.filter(channel => channel.target.node === nodeIndex);
355
+ }
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Gets the transformation matrix for a GLTF node
361
+ * @param {GLTFNode} node - The GLTF node
362
+ * @returns {mat4} The transformation matrix
363
+ */
364
+ function getTransformationMatrix(node) {
365
+ // TRS
366
+ const translation = node.translation ?? vec3.create();
367
+ const rotation = node.rotation ?? quat.create();
368
+ const scale = node.scale ?? vec3.fromValues(1.0, 1.0, 1.0);
369
+ const matrix = node.matrix !== undefined ? mat4.clone(node.matrix) : mat4.fromRotationTranslationScale(mat4.create(), rotation, translation, scale);
370
+ return matrix;
371
+ }
372
+
373
+ /**
374
+ * Processes a GLTF node
375
+ * @param {GLTFnode} node - The GLTF node
376
+ * @param {object} model The model object
377
+ * @param {vtkActor} parentActor The parent actor
378
+ * @param {mat4} parentMatrix The parent matrix
379
+ */
380
+ async function processNode(node, model) {
381
+ let parentActor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
382
+ let parentMatrix = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : mat4.create();
383
+ node.transform = getTransformationMatrix(node);
384
+ const worldMatrix = mat4.multiply(mat4.create(), parentMatrix, node.transform);
385
+
386
+ // Create actor for the current node
387
+ const actor = await createActorFromGTLFNode(model, node, worldMatrix);
388
+ if (actor) {
389
+ actor.setUserMatrix(worldMatrix);
390
+ if (parentActor) {
391
+ actor.setParentProp(parentActor);
392
+ }
393
+ model.actors.set(node.id, actor);
394
+ }
395
+
396
+ // Handle KHRLightsPunctual extension
397
+ if (node.extensions?.KHR_lights_punctual) {
398
+ handleKHRLightsPunctual(node.extensions.KHR_lights_punctual, node.transform, model);
399
+ }
400
+ if (node.children && Array.isArray(node.children) && node.children.length > 0) {
401
+ await Promise.all(node.children.map(async child => {
402
+ const parent = model.actors.get(node.id);
403
+ await processNode(child, model, parent, worldMatrix);
404
+ }));
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Creates VTK actors from a GLTF object
410
+ * @param {glTF} glTF - The GLTF object
411
+ * @param {number} sceneId - The scene index to create actors for
412
+ * @returns {vtkActor[]} The created VTK actors
413
+ */
414
+ async function createVTKObjects(model) {
415
+ model.animations = model.glTFTree.animations?.map(createGLTFAnimation);
416
+ const extensionsNames = Object.keys(model.glTFTree?.extensions || []);
417
+ extensionsNames.forEach(extensionName => {
418
+ const extension = model.glTFTree.extensions[extensionName];
419
+ switch (extensionName) {
420
+ case 'KHR_materials_variants':
421
+ handleKHRMaterialsVariants(extension, model);
422
+ break;
423
+ case 'KHR_draco_mesh_compression':
424
+ break;
425
+ default:
426
+ vtkWarningMacro(`Unhandled extension: ${extensionName}`);
427
+ }
428
+ });
429
+
430
+ // Get the sceneId to process
431
+ const sceneId = model.sceneId ?? model.glTFTree.scene;
432
+ if (model.glTFTree.scenes?.length && model.glTFTree.scenes[sceneId]?.nodes) {
433
+ await Promise.all(model.glTFTree.scenes[sceneId].nodes.map(async node => {
434
+ if (node) {
435
+ await processNode(node, model);
436
+ } else {
437
+ vtkWarningMacro(`Node not found in glTF.nodes`);
438
+ }
439
+ }));
440
+ } else {
441
+ vtkWarningMacro('No valid scenes found in the glTF data');
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Sets up the camera for a vtk renderer based on the bounds of the given actors.
447
+ *
448
+ * @param {GLTCamera} camera - The GLTF camera object
449
+ */
450
+ function GLTFCameraToVTKCamera(glTFCamera) {
451
+ const camera = vtkCamera.newInstance();
452
+ if (glTFCamera.type === 'perspective') {
453
+ const {
454
+ yfov,
455
+ znear,
456
+ zfar
457
+ } = glTFCamera.perspective;
458
+ camera.setClippingRange(znear, zfar);
459
+ camera.setParallelProjection(false);
460
+ camera.setViewAngle(degreesFromRadians(yfov));
461
+ } else if (glTFCamera.type === 'orthographic') {
462
+ const {
463
+ ymag,
464
+ znear,
465
+ zfar
466
+ } = glTFCamera.orthographic;
467
+ camera.setClippingRange(znear, zfar);
468
+ camera.setParallelProjection(true);
469
+ camera.setParallelScale(ymag);
470
+ } else {
471
+ throw new Error('Unsupported camera type');
472
+ }
473
+ return camera;
474
+ }
475
+
476
+ /**
477
+ *
478
+ * @param {vtkCamera} camera
479
+ * @param {*} transformMatrix
480
+ */
481
+ function applyTransformToCamera(camera, transformMatrix) {
482
+ if (!camera || !transformMatrix) {
483
+ return;
484
+ }
485
+
486
+ // At identity, camera position is origin, +y up, -z view direction
487
+ const position = [0, 0, 0];
488
+ const viewUp = [0, 1, 0];
489
+ const focus = [0, 0, -1];
490
+ const t = vtkTransform.newInstance();
491
+ t.setMatrix(transformMatrix);
492
+
493
+ // Transform position
494
+ t.transformPoint(position, position);
495
+ t.transformPoints(viewUp, viewUp);
496
+ t.transformPoints(focus, focus);
497
+ focus[0] += position[0];
498
+ focus[1] += position[1];
499
+ focus[2] += position[2];
500
+
501
+ // Apply the transformed values to the camera
502
+ camera.setPosition(position);
503
+ camera.setFocalPoint(focus);
504
+ camera.setViewUp(viewUp);
505
+ }
506
+
507
+ export { GLTFCameraToVTKCamera, applyTransformToCamera, createPropertyFromGLTFMaterial, createVTKObjects, parseGLTF };
@@ -0,0 +1,165 @@
1
+ import WebworkerPromise from 'webworker-promise';
2
+ import { m as macro } from '../../../macros2.js';
3
+ 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
+ import { ARRAY_TYPES, COMPONENTS, BYTES, GL_SAMPLER } from './Constants.js';
6
+
7
+ const {
8
+ vtkWarningMacro,
9
+ vtkErrorMacro
10
+ } = macro;
11
+
12
+ /**
13
+ * Get GL enum from sampler parameter
14
+ * @param {*} parameter The sampler parameter
15
+ * @returns The GL enum
16
+ */
17
+ function getGLEnumFromSamplerParameter(parameter) {
18
+ const GL_TEXTURE_MAG_FILTER = 0x2800;
19
+ const GL_TEXTURE_MIN_FILTER = 0x2801;
20
+ const GL_TEXTURE_WRAP_S = 0x2802;
21
+ const GL_TEXTURE_WRAP_T = 0x2803;
22
+ const Mapping = {
23
+ magFilter: GL_TEXTURE_MAG_FILTER,
24
+ minFilter: GL_TEXTURE_MIN_FILTER,
25
+ wrapS: GL_TEXTURE_WRAP_S,
26
+ wrapT: GL_TEXTURE_WRAP_T
27
+ };
28
+ return Mapping[parameter];
29
+ }
30
+ function getAccessorArrayTypeAndLength(accessor, bufferView) {
31
+ const ArrayType = ARRAY_TYPES[accessor.componentType];
32
+ const components = COMPONENTS[accessor.type];
33
+ const bytesPerComponent = BYTES[accessor.componentType];
34
+ const length = accessor.count * components;
35
+ const byteLength = accessor.count * components * bytesPerComponent;
36
+ return {
37
+ ArrayType,
38
+ length,
39
+ byteLength
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Resolves a URL based on the original path
45
+ * @param {*} url The URL to resolve
46
+ * @param {*} originalPath The original path to resolve the URL against
47
+ * @returns The resolved URL or an empty string if the URL is invalid
48
+ */
49
+ function resolveUrl(url, originalPath) {
50
+ // Invalid URL
51
+ if (typeof url !== 'string' || url === '') return '';
52
+ try {
53
+ // Data URI
54
+ if (url.startsWith('data:')) return url;
55
+
56
+ // Blob URL
57
+ if (url.startsWith('blob:')) return url;
58
+
59
+ // Create URL object from the original path
60
+ const baseUrl = new URL(originalPath);
61
+ if (!baseUrl.pathname.includes('.') && !baseUrl.pathname.endsWith('/')) {
62
+ baseUrl.pathname += '/';
63
+ }
64
+
65
+ // Absolute URL (http://, https://, //)
66
+ if (url.startsWith('http:') || url.startsWith('https:') || url.startsWith('//')) {
67
+ return new URL(url, baseUrl).href;
68
+ }
69
+
70
+ // Host Relative URL
71
+ if (url.startsWith('/')) {
72
+ return new URL(url, baseUrl).href;
73
+ }
74
+
75
+ // Relative URL
76
+ return new URL(url, baseUrl).href;
77
+ } catch (error) {
78
+ vtkErrorMacro('Error resolving URL:', error);
79
+ return '';
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Loads image from buffer or URI
85
+ * @param {*} image
86
+ * @param {*} channel
87
+ * @returns
88
+ */
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());
101
+ 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();
112
+ });
113
+ }
114
+ if (image.uri) {
115
+ vtkWarningMacro('Falling back to image uri', image.uri);
116
+ return new Promise((resolve, reject) => {
117
+ const img = new Image();
118
+ img.crossOrigin = 'Anonymous';
119
+ img.onload = () => {
120
+ image.cache[channel] = img; // Cache the loaded image based on the channel
121
+ resolve(img);
122
+ };
123
+ img.onerror = reject;
124
+ img.src = image.uri;
125
+ });
126
+ }
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ *
132
+ * @param {*} image
133
+ * @param {*} sampler
134
+ * @param {*} extensions
135
+ * @returns
136
+ */
137
+ function createVTKTextureFromGLTFTexture(image, sampler, extensions) {
138
+ const texture = vtkTexture.newInstance();
139
+ // Apply sampler settings
140
+ if (sampler) {
141
+ if ('wrapS' in sampler && 'wrapT' in sampler || 'minFilter' in sampler && 'magFilter' in sampler) {
142
+ if (sampler.wrapS === GL_SAMPLER.CLAMP_TO_EDGE || sampler.wrapT === GL_SAMPLER.CLAMP_TO_EDGE) {
143
+ texture.setRepeat(false);
144
+ texture.setEdgeClamp(true);
145
+ } else if (sampler.wrapS === GL_SAMPLER.REPEAT || sampler.wrapT === GL_SAMPLER.REPEAT) {
146
+ texture.setRepeat(true);
147
+ texture.setEdgeClamp(false);
148
+ } else {
149
+ vtkWarningMacro('Mirrored texture wrapping is not supported!');
150
+ }
151
+ const linearFilters = [GL_SAMPLER.LINEAR, GL_SAMPLER.LINEAR_MIPMAP_NEAREST, GL_SAMPLER.NEAREST_MIPMAP_LINEAR, GL_SAMPLER.LINEAR_MIPMAP_LINEAR];
152
+ if (linearFilters.includes(sampler.minFilter) || linearFilters.includes(sampler.magFilter)) {
153
+ texture.setInterpolate(true);
154
+ }
155
+ } else {
156
+ texture.MipmapOn();
157
+ texture.setInterpolate(true);
158
+ texture.setEdgeClamp(true);
159
+ }
160
+ }
161
+ texture.setJsImageData(image);
162
+ return texture;
163
+ }
164
+
165
+ export { createVTKTextureFromGLTFTexture, getAccessorArrayTypeAndLength, getGLEnumFromSamplerParameter, loadImage, resolveUrl };