@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,359 @@
1
+ import { m as macro } from '../../../macros2.js';
2
+ import { SEMANTIC_ATTRIBUTE_MAP, MODES, ALPHA_MODE, BYTES, COMPONENTS, DEFAULT_SAMPLER, GL_SAMPLER } from './Constants.js';
3
+ import { getAccessorArrayTypeAndLength, resolveUrl, getGLEnumFromSamplerParameter } from './Utils.js';
4
+
5
+ /* eslint-disable guard-for-in */
6
+ const {
7
+ vtkDebugMacro,
8
+ vtkWarningMacro
9
+ } = macro;
10
+ class GLTFParser {
11
+ constructor(glTF) {
12
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
13
+ const {
14
+ json,
15
+ baseUri = ''
16
+ } = glTF;
17
+ this.glTF = glTF;
18
+ this.options = options;
19
+ this.baseUri = baseUri;
20
+ this.json = json;
21
+ this.extensions = json.extensions || {};
22
+ this.extensionsUsed = json.extensionsUsed || [];
23
+ }
24
+ async parse() {
25
+ const buffers = this.json.buffers || [];
26
+ this.buffers = new Array(buffers.length).fill(null);
27
+ const images = this.json.images || [];
28
+ this.images = new Array(images.length).fill({});
29
+ await this.loadBuffers();
30
+ await this.loadImages();
31
+ this.resolveTree();
32
+ return this.glTF.json;
33
+ }
34
+ resolveTree() {
35
+ this.json.scenes = this.json.scenes?.map((scene, idx) => this.resolveScene(scene, idx));
36
+ this.json.cameras = this.json.cameras?.map((camera, idx) => this.resolveCamera(camera, idx));
37
+ this.json.bufferViews = this.json.bufferViews?.map((bufView, idx) => this.resolveBufferView(bufView, idx));
38
+ this.json.images = this.json.images?.map((image, idx) => this.resolveImage(image, idx));
39
+ this.json.samplers = this.json.samplers?.map((sampler, idx) => this.resolveSampler(sampler, idx));
40
+ this.json.textures = this.json.textures?.map((texture, idx) => this.resolveTexture(texture, idx));
41
+ this.json.accessors = this.json.accessors?.map((accessor, idx) => this.resolveAccessor(accessor, idx));
42
+ this.json.materials = this.json.materials?.map((material, idx) => this.resolveMaterial(material, idx));
43
+ this.json.meshes = this.json.meshes?.map((mesh, idx) => this.resolveMesh(mesh, idx));
44
+ this.json.nodes = this.json.nodes?.map((node, idx) => this.resolveNode(node, idx));
45
+ this.json.skins = this.json.skins?.map((skin, idx) => this.resolveSkin(skin, idx));
46
+ this.json.animations = this.json.animations?.map((animation, idx) => this.resolveAnimation(animation, idx));
47
+ }
48
+ get(array, index) {
49
+ // check if already resolved
50
+ if (typeof index === 'object') {
51
+ return index;
52
+ }
53
+ const object = this.json[array] && this.json[array][index];
54
+ if (!object) {
55
+ vtkWarningMacro(`glTF file error: Could not find ${array}[${index}]`);
56
+ }
57
+ return object;
58
+ }
59
+ resolveScene(scene, index) {
60
+ scene.id = scene.id || `scene-${index}`;
61
+ scene.nodes = (scene.nodes || []).map(node => this.get('nodes', node));
62
+ return scene;
63
+ }
64
+ resolveNode(node, index) {
65
+ node.id = node.id || `node-${index}`;
66
+ if (node.children) {
67
+ node.children = node.children.map(child => this.get('nodes', child));
68
+ }
69
+ if (node.mesh !== undefined) {
70
+ node.mesh = this.get('meshes', node.mesh);
71
+ } else if (node.meshes !== undefined && node.meshes.length) {
72
+ node.mesh = node.meshes.reduce((accum, meshIndex) => {
73
+ const mesh = this.get('meshes', meshIndex);
74
+ accum.id = mesh.id;
75
+ accum.primitives = accum.primitives.concat(mesh.primitives);
76
+ return accum;
77
+ }, {
78
+ primitives: []
79
+ });
80
+ }
81
+ if (node.camera !== undefined) {
82
+ node.camera = this.get('cameras', node.camera);
83
+ }
84
+ if (node.skin !== undefined) {
85
+ node.skin = this.get('skins', node.skin);
86
+ }
87
+
88
+ // Fill punctual lights objects
89
+ if (node.extensions?.KHR_lights_punctual) {
90
+ node.extensions.KHR_lights_punctual.light = this.extensions?.KHR_lights_punctual.lights[node.extensions.KHR_lights_punctual.light];
91
+ }
92
+ return node;
93
+ }
94
+ resolveSkin(skin, index) {
95
+ skin.id = skin.id || `skin-${index}`;
96
+ skin.inverseBindMatrices = this.get('accessors', skin.inverseBindMatrices);
97
+ return skin;
98
+ }
99
+ resolveMesh(mesh, index) {
100
+ mesh.id = mesh.id || `mesh-${index}`;
101
+ if (mesh.primitives) {
102
+ mesh.primitives = mesh.primitives.map((primitive, idx) => {
103
+ const attributes = primitive.attributes;
104
+ primitive.name = `submesh-${idx}`;
105
+ primitive.attributes = {};
106
+ for (const attribute in attributes) {
107
+ const attr = SEMANTIC_ATTRIBUTE_MAP[attribute];
108
+ primitive.attributes[attr] = this.get('accessors', attributes[attribute]);
109
+ }
110
+ if (primitive.indices !== undefined) {
111
+ primitive.indices = this.get('accessors', primitive.indices);
112
+ }
113
+ if (primitive.material !== undefined) {
114
+ primitive.material = this.get('materials', primitive.material);
115
+ }
116
+ if (primitive.mode === undefined) {
117
+ primitive.mode = MODES.GL_TRIANGLES; // Default one
118
+ }
119
+
120
+ if (primitive.extensions?.KHR_draco_mesh_compression) {
121
+ vtkDebugMacro('Using Draco mesh compression');
122
+ const bufferView = this.get('bufferViews', primitive.extensions.KHR_draco_mesh_compression.bufferView);
123
+ primitive.extensions.KHR_draco_mesh_compression.bufferView = bufferView.data;
124
+ }
125
+ return primitive;
126
+ });
127
+ }
128
+ return mesh;
129
+ }
130
+ resolveMaterial(material, index) {
131
+ material.id = material.id || `material-${index}`;
132
+ if (material.alphaMode === undefined) material.alphaMode = ALPHA_MODE.OPAQUE;
133
+ if (material.doubleSided === undefined) material.doubleSided = false;
134
+ if (material.alphaCutoff === undefined) material.alphaCutoff = 0.5;
135
+ if (material.normalTexture) {
136
+ material.normalTexture = {
137
+ ...material.normalTexture
138
+ };
139
+ material.normalTexture.texture = this.get('textures', material.normalTexture.index);
140
+ }
141
+ if (material.occlusionTexture) {
142
+ material.occlusionTexture = {
143
+ ...material.occlusionTexture
144
+ };
145
+ material.occlusionTexture.texture = this.get('textures', material.occlusionTexture.index);
146
+ }
147
+ if (material.emissiveTexture) {
148
+ material.emissiveTexture = {
149
+ ...material.emissiveTexture
150
+ };
151
+ material.emissiveTexture.texture = this.get('textures', material.emissiveTexture.index);
152
+ }
153
+ if (!material.emissiveFactor) {
154
+ material.emissiveFactor = material.emissiveTexture ? 1 : 0;
155
+ } else material.emissiveFactor = material.emissiveFactor[0];
156
+ if (material.pbrMetallicRoughness) {
157
+ material.pbrMetallicRoughness = {
158
+ ...material.pbrMetallicRoughness
159
+ };
160
+ const mr = material.pbrMetallicRoughness;
161
+ if (mr.baseColorTexture) {
162
+ mr.baseColorTexture = {
163
+ ...mr.baseColorTexture
164
+ };
165
+ mr.baseColorTexture.texture = this.get('textures', mr.baseColorTexture.index);
166
+ }
167
+ if (mr.metallicRoughnessTexture) {
168
+ mr.metallicRoughnessTexture = {
169
+ ...mr.metallicRoughnessTexture
170
+ };
171
+ mr.metallicRoughnessTexture.texture = this.get('textures', mr.metallicRoughnessTexture.index);
172
+ }
173
+ } else {
174
+ material.pbrMetallicRoughness = {
175
+ baseColorFactor: [1, 1, 1, 1],
176
+ metallicFactor: 1.0,
177
+ roughnessFactor: 1.0
178
+ };
179
+ }
180
+ return material;
181
+ }
182
+
183
+ /**
184
+ * Take values of particular accessor from interleaved buffer various parts of
185
+ * the buffer
186
+ */
187
+ getValueFromInterleavedBuffer(buffer, byteOffset, byteStride, bytesPerElement, count) {
188
+ const result = new Uint8Array(count * bytesPerElement);
189
+ for (let i = 0; i < count; i++) {
190
+ const elementOffset = byteOffset + i * byteStride;
191
+ result.set(new Uint8Array(buffer.arrayBuffer.slice(elementOffset, elementOffset + bytesPerElement)), i * bytesPerElement);
192
+ }
193
+ return result.buffer;
194
+ }
195
+ resolveAccessor(accessor, index) {
196
+ accessor.id = accessor.id || `accessor-${index}`;
197
+ if (accessor.bufferView !== undefined) {
198
+ // Draco encoded meshes don't have bufferView
199
+ accessor.bufferView = this.get('bufferViews', accessor.bufferView);
200
+ }
201
+
202
+ // Look up enums
203
+ accessor.bytesPerComponent = BYTES[accessor.componentType];
204
+ accessor.components = COMPONENTS[accessor.type];
205
+ accessor.bytesPerElement = accessor.bytesPerComponent * accessor.components;
206
+
207
+ // Create TypedArray for the accessor
208
+ // Note: The canonical way to instantiate is to ignore this array and create
209
+ // WebGLBuffer's using the bufferViews.
210
+ if (accessor.bufferView) {
211
+ const buffer = accessor.bufferView.buffer;
212
+ const {
213
+ ArrayType,
214
+ byteLength
215
+ } = 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);
218
+ if (accessor.bufferView.byteStride) {
219
+ slicedBufffer = this.getValueFromInterleavedBuffer(buffer, byteOffset, accessor.bufferView.byteStride, accessor.bytesPerElement, accessor.count);
220
+ }
221
+ accessor.value = new ArrayType(slicedBufffer);
222
+ }
223
+ return accessor;
224
+ }
225
+ resolveTexture(texture, index) {
226
+ texture.id = texture.id || `texture-${index}`;
227
+ texture.sampler = 'sampler' in texture ? this.get('samplers', texture.sampler) : DEFAULT_SAMPLER;
228
+ texture.source = this.get('images', texture.source);
229
+
230
+ // Handle texture extensions sources
231
+ if (texture.extensions !== undefined) {
232
+ const extensionsNames = Object.keys(texture.extensions);
233
+ extensionsNames.forEach(extensionName => {
234
+ const extension = texture.extensions[extensionName];
235
+ switch (extensionName) {
236
+ case 'KHR_texture_basisu':
237
+ case 'EXT_texture_webp':
238
+ case 'EXT_texture_avif':
239
+ texture.source = this.get('images', extension.source);
240
+ break;
241
+ default:
242
+ vtkWarningMacro(`Unhandled extension: ${extensionName}`);
243
+ }
244
+ });
245
+ }
246
+ return texture;
247
+ }
248
+ resolveSampler(sampler, index) {
249
+ sampler.id = sampler.id || `sampler-${index}`;
250
+ if (!Object.hasOwn(sampler, 'wrapS')) sampler.wrapS = GL_SAMPLER.REPEAT;
251
+ if (!Object.hasOwn(sampler, 'wrapT')) sampler.wrapT = GL_SAMPLER.REPEAT;
252
+ if (!Object.hasOwn(sampler, 'minFilter')) sampler.minFilter = GL_SAMPLER.LINEAR_MIPMAP_LINEAR;
253
+ if (!Object.hasOwn(sampler, 'magFilter')) sampler.magFilter = GL_SAMPLER.NEAREST;
254
+
255
+ // Map textual parameters to GL parameter values
256
+ sampler.parameters = {};
257
+ for (const key in sampler) {
258
+ const glEnum = getGLEnumFromSamplerParameter(key);
259
+ if (glEnum !== undefined) {
260
+ sampler.parameters[glEnum] = sampler[key];
261
+ }
262
+ }
263
+ return sampler;
264
+ }
265
+ resolveImage(image, index) {
266
+ image.id = image.id || `image-${index}`;
267
+ if (image.bufferView !== undefined) {
268
+ image.bufferView = this.get('bufferViews', image.bufferView);
269
+ }
270
+ return image;
271
+ }
272
+ resolveBufferView(bufferView, index) {
273
+ bufferView.id = bufferView.id || `bufferView-${index}`;
274
+ const bufferIndex = bufferView.buffer;
275
+ bufferView.buffer = this.buffers[bufferIndex];
276
+ const arrayBuffer = this.buffers[bufferIndex].arrayBuffer;
277
+ let byteOffset = this.buffers[bufferIndex].byteOffset || 0;
278
+ if ('byteOffset' in bufferView) {
279
+ byteOffset += bufferView.byteOffset;
280
+ }
281
+ bufferView.data = new Uint8Array(arrayBuffer, byteOffset, bufferView.byteLength);
282
+ return bufferView;
283
+ }
284
+ resolveCamera(camera, index) {
285
+ camera.id = camera.id || `camera-${index}`;
286
+ return camera;
287
+ }
288
+ resolveAnimation(animation, index) {
289
+ animation.id = animation.id || `animation-${index}`;
290
+ animation.samplers.map(sampler => {
291
+ sampler.input = this.get('accessors', sampler.input).value;
292
+ sampler.output = this.get('accessors', sampler.output).value;
293
+ return sampler;
294
+ });
295
+ return animation;
296
+ }
297
+ loadBuffers() {
298
+ const promises = this.json.buffers.map((buffer, idx) => this.loadBuffer(buffer, idx).then(() => {
299
+ delete buffer.uri;
300
+ }));
301
+ return Promise.all(promises);
302
+ }
303
+ async loadBuffer(buffer, index) {
304
+ let arrayBuffer = buffer;
305
+ if (buffer.uri) {
306
+ vtkDebugMacro('Loading uri', buffer.uri);
307
+ const uri = resolveUrl(buffer.uri, this.options.baseUri);
308
+ const response = await fetch(uri);
309
+ arrayBuffer = await response.arrayBuffer();
310
+ } else if (this.glTF.glbBuffers) {
311
+ arrayBuffer = this.glTF.glbBuffers[index];
312
+ }
313
+ this.buffers[index] = {
314
+ arrayBuffer,
315
+ byteOffset: 0,
316
+ byteLength: arrayBuffer.byteLength
317
+ };
318
+ }
319
+ loadImages() {
320
+ const images = this.json.images || [];
321
+ const promises = [];
322
+ return new Promise((resolve, reject) => {
323
+ for (let i = 0; i < images.length; ++i) {
324
+ promises.push(Promise.resolve(this.loadImage(images[i], i).then(() => {
325
+ vtkDebugMacro('Texture loaded ', images[i]);
326
+ })));
327
+ }
328
+ Promise.all(promises).then(() => resolve(this.images));
329
+ });
330
+ }
331
+ async loadImage(image, index) {
332
+ let arrayBuffer;
333
+ let buffer;
334
+ if (image.uri) {
335
+ vtkDebugMacro('Loading texture', image.uri);
336
+ const uri = resolveUrl(image.uri, this.options.baseUri);
337
+ const response = await fetch(uri);
338
+ arrayBuffer = await response.arrayBuffer();
339
+ image.uri = uri;
340
+ image.bufferView = {
341
+ data: arrayBuffer
342
+ };
343
+ } else if (image.bufferView) {
344
+ const bufferView = this.get('bufferViews', image.bufferView);
345
+ buffer = this.get('buffers', bufferView.buffer);
346
+
347
+ // GLB buffer
348
+ if (this.glTF.glbBuffers) {
349
+ buffer = this.glTF.glbBuffers[bufferView.buffer];
350
+ arrayBuffer = buffer.slice(bufferView.byteOffset, bufferView.byteOffset + bufferView.byteLength);
351
+ }
352
+ image.bufferView = {
353
+ data: arrayBuffer
354
+ };
355
+ }
356
+ }
357
+ }
358
+
359
+ export { GLTFParser as default };