@shapediver/viewer.data-engine.gltf-converter 3.3.4 → 3.3.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapediver/viewer.data-engine.gltf-converter",
3
- "version": "3.3.4",
3
+ "version": "3.3.7",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "Michael Oppitz <michael@shapediver.com>",
@@ -10,11 +10,10 @@
10
10
  "test": "__tests__"
11
11
  },
12
12
  "files": [
13
- "dist",
14
- "src",
15
13
  "package.json",
14
+ "dist/",
16
15
  "README.md",
17
- "tsconfig.json"
16
+ "LICENSE"
18
17
  ],
19
18
  "publishConfig": {
20
19
  "access": "public"
@@ -39,14 +38,14 @@
39
38
  "testEnvironment": "node"
40
39
  },
41
40
  "dependencies": {
42
- "@shapediver/viewer.data-engine.shared-types": "3.3.4",
43
- "@shapediver/viewer.shared.build-data": "3.3.4",
44
- "@shapediver/viewer.shared.global-access-objects": "3.3.4",
45
- "@shapediver/viewer.shared.node-tree": "3.3.4",
46
- "@shapediver/viewer.shared.services": "3.3.4",
47
- "@shapediver/viewer.shared.types": "3.3.4",
41
+ "@shapediver/viewer.data-engine.shared-types": "3.3.7",
42
+ "@shapediver/viewer.shared.build-data": "3.3.7",
43
+ "@shapediver/viewer.shared.global-access-objects": "3.3.7",
44
+ "@shapediver/viewer.shared.node-tree": "3.3.7",
45
+ "@shapediver/viewer.shared.services": "3.3.7",
46
+ "@shapediver/viewer.shared.types": "3.3.7",
48
47
  "axios": "^1.2.6",
49
48
  "gl-matrix": "3.3.0"
50
49
  },
51
- "gitHead": "8193da527b4e3fc4d90181018bd60d6ac70be3e8"
50
+ "gitHead": "112787d5c5226cca5e89d08102d0b1a3dd4a1d71"
52
51
  }
@@ -1,969 +0,0 @@
1
- import {
2
- atobCustom,
3
- Converter,
4
- EventEngine,
5
- EVENTTYPE,
6
- UuidGenerator
7
- } from '@shapediver/viewer.shared.services';
8
- import { build_data } from '@shapediver/viewer.shared.build-data';
9
- import { GlobalAccessObjects } from '@shapediver/viewer.shared.global-access-objects';
10
- import { ITreeNode, TreeNode } from '@shapediver/viewer.shared.node-tree';
11
- import { mat4, vec3 } from 'gl-matrix';
12
- import {
13
- IGLTF_v2,
14
- IGLTF_v2_Scene,
15
- IGLTF_v2_Node,
16
- IGLTF_v2_Material,
17
- IGLTF_v2_Material_KHR_materials_pbrSpecularGlossiness,
18
- IGLTF_v2_Primitive,
19
- IGLTF_v2_Mesh,
20
- IGLTF_v2_Accessor,
21
- ACCESSORCOMPONENTSIZE_V2,
22
- IGLTF_v2_BufferView,
23
- IGLTF_v2_Texture,
24
- IGLTF_v2_Image,
25
- IGLTF_v2_Animation,
26
- } from '@shapediver/viewer.data-engine.shared-types';
27
- import {
28
- AttributeData,
29
- GeometryData,
30
- MapData,
31
- MATERIAL_ALPHA,
32
- MATERIAL_SIDE,
33
- MaterialStandardData,
34
- AnimationData,
35
- PRIMITIVE_MODE,
36
- MaterialSpecularGlossinessData,
37
- MaterialUnlitData,
38
- IMaterialAbstractData,
39
- IMapData,
40
- IPrimitiveData,
41
- IAttributeData,
42
- IAnimationData,
43
- IGeometryData,
44
- ITaskEvent,
45
- TASK_TYPE,
46
- } from '@shapediver/viewer.shared.types';
47
-
48
- // #region Classes (1)
49
-
50
- export class GLTFConverter {
51
- // #region Properties (23)
52
-
53
- private readonly _converter: Converter = Converter.instance;
54
- private readonly _eventEngine: EventEngine = EventEngine.instance;
55
- private readonly _globalAccessObjects: GlobalAccessObjects = GlobalAccessObjects.instance;
56
- private readonly _globalTransformationInverse = mat4.fromValues(
57
- 1, 0, 0, 0,
58
- 0, 0, -1, 0,
59
- 0, 1, 0, 0,
60
- 0, 0, 0, 1);
61
- private readonly _progressUpdateLimit = 500;
62
- private readonly _uuidGenerator: UuidGenerator = UuidGenerator.instance;
63
-
64
- private static _instance: GLTFConverter;
65
-
66
- private _animations: IAnimationData[] = [];
67
- private _buffers: ArrayBuffer[] = [];
68
- private _byteOffset: number = 0;
69
- private _content: IGLTF_v2 = {
70
- asset: {
71
- copyright: '2023 (c) ShapeDiver',
72
- generator: 'ShapeDiverViewer@' + build_data.build_version,
73
- version: '2.0',
74
- extensions: {}
75
- },
76
- };
77
- private _convertForAR = false;
78
- private _eventId = '';
79
- private _extensionsRequired: string[] = [];
80
- private _extensionsUsed: string[] = [];
81
- private _imageCache: { [key: string]: number } = {};
82
- private _materialCache: {
83
- [key: string]: number
84
- } = {};
85
- private _meshCache: {
86
- [key: string]: number
87
- } = {};
88
- private _nodes: {
89
- node: ITreeNode,
90
- id: number
91
- }[] = [];
92
- private _numberOfNodes = 0;
93
- private _progressTimer = 0;
94
- private _promises: Promise<unknown>[] = [];
95
- private _viewport?: string;
96
-
97
- // #endregion Properties (23)
98
-
99
- // #region Public Static Getters And Setters (1)
100
-
101
- public static get instance() {
102
- return this._instance || (this._instance = new this());
103
- }
104
-
105
- // #endregion Public Static Getters And Setters (1)
106
-
107
- // #region Public Methods (1)
108
-
109
- public async convert(node: ITreeNode, convertForAR = false, viewport?: string): Promise<ArrayBuffer> {
110
- this._eventId = this._uuidGenerator.create();
111
- const eventStart: ITaskEvent = { type: TASK_TYPE.GLTF_CREATION, id: this._eventId, progress: 0, status: 'Starting glTF conversion.' };
112
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_START, eventStart);
113
-
114
- this._numberOfNodes = 0;
115
- node.traverse(() => this._numberOfNodes++);
116
-
117
- this._progressTimer = performance.now();
118
-
119
- this.reset();
120
-
121
- this._convertForAR = convertForAR;
122
- this._viewport = viewport;
123
- const originalParent = node.parent;
124
-
125
- const sceneNode = new TreeNode('ShapeDiverRootNode');
126
- sceneNode.addChild(node);
127
-
128
- const sceneDef: IGLTF_v2_Scene = {
129
- name: sceneNode.name,
130
- nodes: []
131
- };
132
-
133
- const globalTransformationInverseId = this._uuidGenerator.create();
134
- node.addTransformation({
135
- id: globalTransformationInverseId,
136
- matrix: this._globalTransformationInverse,
137
- });
138
-
139
- const translationMatrixId = this._uuidGenerator.create();
140
- if (convertForAR) {
141
- // add translation matrix to scene tree node
142
- const center = node.boundingBox.boundingSphere.center;
143
- const translationMatrix: mat4 = mat4.fromTranslation(mat4.create(), vec3.multiply(vec3.create(), vec3.fromValues(center[0], center[1], center[2]), vec3.fromValues(-1, -1, -1)));
144
- node.addTransformation({ id: translationMatrixId, matrix: translationMatrix });
145
- }
146
-
147
- if (this._viewport) {
148
- if (this._viewport && node.excludeViewports.includes(this._viewport) === false && (node.restrictViewports.length > 0 && !node.restrictViewports.includes(this._viewport)) === false) {
149
- const nodeId = await this.convertNode(node);
150
- if (nodeId !== -1) sceneDef.nodes?.push(nodeId);
151
- }
152
- } else {
153
- const nodeId = await this.convertNode(node);
154
- if (nodeId !== -1) sceneDef.nodes?.push(nodeId);
155
- }
156
-
157
- for (let i = 0; i < node.transformations.length; i++)
158
- if (node.transformations[i].id === globalTransformationInverseId)
159
- node.removeTransformation(node.transformations[i]);
160
-
161
- if (convertForAR) {
162
- // remove translation the matrix
163
- for (let i = 0; i < node.transformations.length; i++)
164
- if (node.transformations[i].id === translationMatrixId)
165
- node.removeTransformation(node.transformations[i]);
166
- }
167
-
168
- this._content.scenes = [];
169
- this._content.scenes.push(sceneDef);
170
-
171
- this.convertAnimations();
172
-
173
- // Declare extensions.
174
- if (this._extensionsUsed.length > 0) this._content.extensionsUsed = this._extensionsUsed;
175
- if (this._extensionsRequired.length > 0) this._content.extensionsRequired = this._extensionsRequired;
176
-
177
- let promisesLength = 0;
178
- while (promisesLength !== this._promises.length) {
179
- promisesLength = this._promises.length;
180
- await Promise.all(this._promises);
181
- await new Promise(resolve => setTimeout(resolve, 0));
182
- }
183
-
184
- const eventProgressImagePromises: ITaskEvent = { type: TASK_TYPE.GLTF_CREATION, id: this._eventId, progress: 0.75, status: 'GlTF images resolved.' };
185
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventProgressImagePromises);
186
-
187
- // Merge buffers.
188
- const blob = new Blob(this._buffers, { type: 'application/octet-stream' });
189
-
190
- if (originalParent)
191
- originalParent.addChild(node);
192
-
193
- // Update byte length of the single buffer.
194
- if (this._content.buffers && this._content.buffers.length > 0) this._content.buffers[0].byteLength = blob.size;
195
-
196
- return new Promise<ArrayBuffer>((resolve, reject) => {
197
- // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
198
-
199
- try {
200
- if (typeof window !== 'undefined' && window.FileReader) {
201
- const reader = new window.FileReader();
202
- reader.readAsArrayBuffer(blob);
203
- reader.onloadend = () => {
204
- // Binary chunk.
205
- const binaryChunk = this.getPaddedArrayBuffer(<ArrayBuffer>reader.result);
206
- const binaryChunkPrefix = new DataView(new ArrayBuffer(8));
207
- binaryChunkPrefix.setUint32(0, binaryChunk.byteLength, true);
208
- binaryChunkPrefix.setUint32(4, 0x004E4942, true);
209
-
210
- // JSON chunk.
211
- const jsonChunk = this.getPaddedArrayBuffer(this.stringToArrayBuffer(JSON.stringify(this._content)), 0x20);
212
- const jsonChunkPrefix = new DataView(new ArrayBuffer(8));
213
- jsonChunkPrefix.setUint32(0, jsonChunk.byteLength, true);
214
- jsonChunkPrefix.setUint32(4, 0x4E4F534A, true);
215
-
216
- // GLB header.
217
- const header = new ArrayBuffer(12);
218
- const headerView = new DataView(header);
219
- headerView.setUint32(0, 0x46546C67, true);
220
- headerView.setUint32(4, 2, true);
221
- const totalByteLength = 12
222
- + jsonChunkPrefix.byteLength + jsonChunk.byteLength
223
- + binaryChunkPrefix.byteLength + binaryChunk.byteLength;
224
- headerView.setUint32(8, totalByteLength, true);
225
-
226
- const glbBlob = new Blob([
227
- header,
228
- jsonChunkPrefix,
229
- jsonChunk,
230
- binaryChunkPrefix,
231
- binaryChunk
232
- ], { type: 'application/octet-stream' });
233
-
234
- const glbReader = new window.FileReader();
235
- glbReader.readAsArrayBuffer(glbBlob);
236
- glbReader.onloadend = () => {
237
- const eventEnd: ITaskEvent = { type: TASK_TYPE.GLTF_CREATION, id: this._eventId, progress: 1, status: 'GlTF creation complete.' };
238
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_END, eventEnd);
239
- resolve(<ArrayBuffer>glbReader.result);
240
- };
241
- glbReader.onerror = reject;
242
- };
243
-
244
- reader.onerror = reject;
245
- } else {
246
- reject('FileReader not available.');
247
- }
248
- } catch (e) {
249
- reject(e);
250
- }
251
- });
252
- }
253
-
254
- // #endregion Public Methods (1)
255
-
256
- // #region Private Methods (17)
257
-
258
- private convertAccessor(data: IAttributeData): number {
259
- if (!this._content.accessors) this._content.accessors = [];
260
-
261
- const bufferView = this.convertBufferView(data);
262
- const minMax = this.getMinMax(data);
263
-
264
- const accessorDef: IGLTF_v2_Accessor = {
265
- bufferView: bufferView,
266
- byteOffset: 0,
267
- componentType: this.getComponentType(data.array),
268
- normalized: data.normalized,
269
- count: +data.count,
270
- max: minMax.max,
271
- min: minMax.min,
272
- type: this.getType(data.itemSize),
273
- // sparse: { // TODO
274
- // count: number,
275
- // indices: {
276
- // bufferView: number,
277
- // byteOffset?: number,
278
- // componentType: number,
279
- // extensions?: { [id: string]: any },
280
- // extras?: any
281
- // },
282
- // values: {
283
- // bufferView: number,
284
- // byteOffset?: number,
285
- // extensions?: { [id: string]: any },
286
- // extras?: any
287
- // },
288
- // extensions?: { [id: string]: any },
289
- // extras?: any
290
- // },
291
- };
292
-
293
- this._content.accessors.push(accessorDef);
294
- return this._content.accessors.length - 1;
295
- }
296
-
297
- private convertAnimations() {
298
- if (!this._content.animations && this._animations.length > 0) this._content.animations = [];
299
- for (let i = 0; i < this._animations.length; i++) {
300
- const animation = this._animations[i];
301
- const animationDef: IGLTF_v2_Animation = {
302
- name: animation.name || 'animation_' + i,
303
- channels: [],
304
- samplers: []
305
- };
306
-
307
- for (let j = 0; j < animation.tracks.length; j++) {
308
- const track = animation.tracks[j];
309
- const value = this._nodes.find(a => a.node === track.node);
310
- if (!value) continue;
311
-
312
- const inputMin = Math.min(...track.times);
313
- const inputMax = Math.max(...track.times);
314
- const inputData = new AttributeData(
315
- new Float32Array(track.times),
316
- 1,
317
- 4,
318
- 0,
319
- 4,
320
- false,
321
- track.times.length,
322
- [inputMin],
323
- [inputMax]);
324
-
325
- const outputMin = [];
326
- outputMin.push(Math.min(...track.values.filter((s, i) => i % (track.path === 'rotation' ? 4 : 3) === 0)));
327
- outputMin.push(Math.min(...track.values.filter((s, i) => i % (track.path === 'rotation' ? 4 : 3) === 1)));
328
- outputMin.push(Math.min(...track.values.filter((s, i) => i % (track.path === 'rotation' ? 4 : 3) === 2)));
329
-
330
- if (track.path === 'rotation') {
331
- outputMin.push(Math.min(...track.values.filter((s, i) => i % 4 === 3)));
332
- }
333
-
334
- const outputMax = [];
335
- outputMax.push(Math.max(...track.values.filter((s, i) => i % (track.path === 'rotation' ? 4 : 3) === 0)));
336
- outputMax.push(Math.max(...track.values.filter((s, i) => i % (track.path === 'rotation' ? 4 : 3) === 1)));
337
- outputMax.push(Math.max(...track.values.filter((s, i) => i % (track.path === 'rotation' ? 4 : 3) === 2)));
338
-
339
- if (track.path === 'rotation') {
340
- outputMax.push(Math.max(...track.values.filter((s, i) => i % 4 === 3)));
341
- }
342
-
343
- const outputData = new AttributeData(
344
- new Float32Array(track.values),
345
- track.path === 'rotation' ? 4 : 3, //itemSize
346
- track.path === 'rotation' ? 16 : 12, //itemBytes
347
- 0,
348
- 4,
349
- false,
350
- track.times.length,
351
- outputMin,
352
- outputMax,
353
- track.path === 'rotation' ? 16 : 12);
354
-
355
- const samplerDef: {
356
- input: number,
357
- interpolation?: string,
358
- output: number,
359
- } = {
360
- input: this.convertAccessor(inputData),
361
- output: this.convertAccessor(outputData),
362
- interpolation: track.interpolation.toUpperCase()
363
- };
364
- animationDef.samplers.push(samplerDef);
365
-
366
- const channelDef: {
367
- sampler: number,
368
- target: {
369
- node: number,
370
- path: string,
371
- }
372
- } = {
373
- sampler: animationDef.samplers.length - 1,
374
- target: {
375
- node: value.id,
376
- path: track.path
377
- }
378
- };
379
- animationDef.channels.push(channelDef);
380
- }
381
- this._content.animations?.push(animationDef);
382
- }
383
- }
384
-
385
- private convertBuffer(buffer: ArrayBuffer): number {
386
- if (!this._content.buffers) this._content.buffers = [];
387
- if (this._content.buffers.length === 0) this._content.buffers = [{ byteLength: 0 }];
388
- this._buffers.push(buffer);
389
- return 0;
390
- }
391
-
392
- private convertBufferView(data: IAttributeData): number {
393
- if (!this._content.bufferViews) this._content.bufferViews = [];
394
- const componentTypeNumber = this.getComponentType(data.array);
395
- const componentSize = ACCESSORCOMPONENTSIZE_V2[<keyof typeof ACCESSORCOMPONENTSIZE_V2>componentTypeNumber];
396
-
397
- const byteLength = Math.ceil(data.count * data.itemSize * componentSize / 4) * 4;
398
- const dataView = new DataView(new ArrayBuffer(byteLength));
399
- let offset = 0;
400
-
401
- for (let i = 0; i < data.count; i++) {
402
- for (let a = 0; a < data.itemSize; a++) {
403
- let value = 0;
404
- if (data.itemSize > 4) {
405
- // no support for interleaved data for itemSize > 4
406
- value = data.array[i * data.itemSize + a];
407
- } else {
408
- if (a === 0) value = data.array[i * data.itemSize];
409
- else if (a === 1) value = data.array[i * data.itemSize + 1];
410
- else if (a === 2) value = data.array[i * data.itemSize + 2];
411
- else if (a === 3) value = data.array[i * data.itemSize + 3];
412
- }
413
-
414
- if (data.array instanceof Float32Array) {
415
- dataView.setFloat32(offset, value, true);
416
- } else if (data.array instanceof Uint32Array) {
417
- dataView.setUint32(offset, value, true);
418
- } else if (data.array instanceof Uint16Array) {
419
- dataView.setUint16(offset, value, true);
420
- } else if (data.array instanceof Int16Array) {
421
- dataView.setInt16(offset, value, true);
422
- } else if (data.array instanceof Uint8Array) {
423
- dataView.setUint8(offset, value);
424
- } else if (data.array instanceof Int8Array) {
425
- dataView.setInt8(offset, value);
426
- }
427
- offset += componentSize;
428
- }
429
- }
430
-
431
- const bufferViewDef: IGLTF_v2_BufferView = {
432
- buffer: this.convertBuffer(dataView.buffer),
433
- byteOffset: this._byteOffset,
434
- byteLength: byteLength,
435
- target: data.target
436
- };
437
- this._byteOffset += byteLength;
438
-
439
- this._content.bufferViews.push(bufferViewDef);
440
- return this._content.bufferViews.length - 1;
441
- }
442
-
443
- private async convertBufferViewImage(blob: Blob): Promise<number> {
444
- if (!this._content.bufferViews) this._content.bufferViews = [];
445
- return new Promise((resolve, reject) => {
446
- try {
447
- if (typeof window !== 'undefined' && window.FileReader) {
448
- const reader = new window.FileReader();
449
- reader.readAsArrayBuffer(blob);
450
- reader.onloadend = () => {
451
- const buffer = this.getPaddedArrayBuffer(<ArrayBuffer>reader.result);
452
- const bufferViewDef = {
453
- buffer: this.convertBuffer(buffer),
454
- byteOffset: this._byteOffset,
455
- byteLength: buffer.byteLength
456
- };
457
- this._byteOffset += buffer.byteLength;
458
- this._content.bufferViews!.push(bufferViewDef);
459
- resolve(this._content.bufferViews!.length - 1);
460
- };
461
- reader.onerror = reject;
462
- } else {
463
- reject('FileReader not available.');
464
- }
465
- } catch (e) {
466
- reject(e);
467
- }
468
- });
469
- }
470
-
471
- private convertImage(data: IMapData): number | undefined {
472
- if (!this._content.images) this._content.images = [];
473
- if (data.image instanceof ArrayBuffer) return;
474
- if (this._imageCache[data.image.src] !== undefined) return this._imageCache[data.image.src];
475
- const imageDef: IGLTF_v2_Image = {};
476
- const canvas = document.createElement('canvas');
477
-
478
- canvas.width = data.image.width;
479
- canvas.height = data.image.height;
480
-
481
- const ctx: CanvasRenderingContext2D = canvas.getContext('2d')!;
482
- if (data.flipY) {
483
- ctx.translate(0, canvas.height);
484
- ctx.scale(1, - 1);
485
- }
486
-
487
- if (data.blob) {
488
- imageDef.mimeType = data.blob.type;
489
- this._promises.push(new Promise<void>((resolve, reject) => {
490
- try {
491
- this.convertBufferViewImage(data.blob!).then(bufferViewIndex => {
492
- imageDef.bufferView = bufferViewIndex;
493
- resolve();
494
- });
495
- } catch (e) {
496
- reject(e);
497
- }
498
- }));
499
- } else {
500
- let mimeType = 'image/png';
501
- if (data.image.src.endsWith('.jpg') || data.image.src.includes('image/jpeg'))
502
- mimeType = 'image/jpeg';
503
-
504
- imageDef.mimeType = mimeType;
505
-
506
- const DATA_URI_REGEX = /^data:(.*?)(;base64)?,(.*)$/;
507
- if (DATA_URI_REGEX.test(data.image.src)) {
508
- const byteString = atobCustom(data.image.src.split(',')[1]);
509
- const mimeType = data.image.src.split(',')[0].split(':')[1].split(';')[0];
510
- const ab = new ArrayBuffer(byteString.length);
511
- const ia = new Uint8Array(ab);
512
- for (let i = 0; i < byteString.length; i++)
513
- ia[i] = byteString.charCodeAt(i);
514
- const blob = new Blob([ab], { type: mimeType });
515
- this._promises.push(new Promise<void>((resolve, reject) => {
516
- try {
517
- this.convertBufferViewImage(blob!)
518
- .then(bufferViewIndex => {
519
- imageDef.bufferView = bufferViewIndex;
520
- resolve();
521
- })
522
- .catch(reject);
523
- } catch (e) {
524
- reject(e);
525
- }
526
- }));
527
- } else {
528
- ctx.drawImage(data.image, 0, 0, canvas.width, canvas.height);
529
- this._promises.push(new Promise<void>((resolve, reject) => {
530
- try {
531
- canvas.toBlob(async (blob) => {
532
- try {
533
- const bufferViewIndex = await this.convertBufferViewImage(blob!);
534
- imageDef.bufferView = bufferViewIndex;
535
- resolve();
536
- } catch (e) {
537
- reject(e);
538
- }
539
- }, mimeType);
540
- } catch (e) {
541
- reject(e);
542
- }
543
- }));
544
- }
545
- }
546
-
547
- this._content.images.push(imageDef);
548
- this._imageCache[data.image.src] = this._content.images.length - 1;
549
- return this._content.images.length - 1;
550
- }
551
-
552
- private convertMaterial(data: IMaterialAbstractData, includeMaps = true): number {
553
- if (!this._content.materials) this._content.materials = [];
554
- if (this._materialCache[data.id + '_' + data.version] !== undefined) return this._materialCache[data.id + '_' + data.version];
555
-
556
- const materialDef: IGLTF_v2_Material = {
557
- name: data.id,
558
- pbrMetallicRoughness: {}
559
- };
560
-
561
- if (data instanceof MaterialSpecularGlossinessData) {
562
- if (!this._extensionsUsed.includes('KHR_materials_pbrSpecularGlossiness'))
563
- this._extensionsUsed.push('KHR_materials_pbrSpecularGlossiness');
564
- if (!this._extensionsRequired.includes('KHR_materials_pbrSpecularGlossiness'))
565
- this._extensionsRequired.push('KHR_materials_pbrSpecularGlossiness');
566
-
567
- const ext: IGLTF_v2_Material_KHR_materials_pbrSpecularGlossiness = {};
568
-
569
- ext.diffuseFactor = this._converter.toColorArray(data.color);
570
- ext.diffuseFactor[3] = data.opacity;
571
- if (data.map && includeMaps) {
572
- const textureIndex = this.convertTexture(data.map);
573
- if (textureIndex !== undefined) ext.diffuseTexture = { index: textureIndex };
574
- }
575
- ext.specularFactor = this._converter.toColorArray(data.specular);
576
- ext.glossinessFactor = data.glossiness;
577
- if (data.specularGlossinessMap && includeMaps) {
578
- const textureIndex = this.convertTexture(data.specularGlossinessMap);
579
- if (textureIndex !== undefined) ext.specularGlossinessTexture = { index: textureIndex };
580
- }
581
-
582
- materialDef.extensions = {
583
- KHR_materials_pbrSpecularGlossiness: ext
584
- };
585
- } else if (data instanceof MaterialUnlitData) {
586
- if (!this._extensionsUsed.includes('KHR_materials_unlit'))
587
- this._extensionsUsed.push('KHR_materials_unlit');
588
- if (!this._extensionsRequired.includes('KHR_materials_unlit'))
589
- this._extensionsRequired.push('KHR_materials_unlit');
590
- materialDef.pbrMetallicRoughness!.baseColorFactor = this._converter.toColorArray(data.color);
591
- materialDef.pbrMetallicRoughness!.baseColorFactor[3] = data.opacity;
592
- if (data.map && includeMaps) {
593
- const textureIndex = this.convertTexture(data.map);
594
- if (textureIndex !== undefined) materialDef.pbrMetallicRoughness!.baseColorTexture = { index: textureIndex };
595
- }
596
-
597
- materialDef.extensions = {
598
- KHR_materials_unlit: {}
599
- };
600
- } else {
601
- const standardMaterialData = data as MaterialStandardData;
602
- materialDef.pbrMetallicRoughness!.baseColorFactor = this._converter.toColorArray(standardMaterialData.color);
603
- materialDef.pbrMetallicRoughness!.baseColorFactor[3] = standardMaterialData.opacity;
604
- if (standardMaterialData.map && includeMaps) {
605
- const textureIndex = this.convertTexture(standardMaterialData.map);
606
- if (textureIndex !== undefined) materialDef.pbrMetallicRoughness!.baseColorTexture = { index: textureIndex };
607
- }
608
- materialDef.pbrMetallicRoughness!.metallicFactor = standardMaterialData.metalnessMap ? 1 : standardMaterialData.metalness;
609
- materialDef.pbrMetallicRoughness!.roughnessFactor = standardMaterialData.roughnessMap ? 1 : standardMaterialData.roughness;
610
- if (standardMaterialData.metalnessRoughnessMap && includeMaps) {
611
- const textureIndex = this.convertTexture(standardMaterialData.metalnessRoughnessMap);
612
- if (textureIndex !== undefined) materialDef.pbrMetallicRoughness!.metallicRoughnessTexture = { index: textureIndex };
613
- } else if (standardMaterialData.metalnessMap && standardMaterialData.roughnessMap && includeMaps) {
614
- if (this._globalAccessObjects.combineTextures) {
615
- this._promises.push(new Promise<void>((resolve, reject) => {
616
- try {
617
- // no support for combining textures
618
- if (!this._globalAccessObjects.combineTextures) return standardMaterialData.roughnessMap;
619
-
620
- this._globalAccessObjects.combineTextures(
621
- undefined,
622
- standardMaterialData.roughnessMap ? standardMaterialData.roughnessMap.image : undefined,
623
- standardMaterialData.metalnessMap ? standardMaterialData.metalnessMap.image : undefined
624
- )
625
- .then(imageData => {
626
- const m = (standardMaterialData.roughnessMap! || standardMaterialData.metalnessMap!)!;
627
-
628
- const mapData = new MapData(imageData.image,
629
- {
630
- blob: imageData.blob,
631
- wrapS: m.wrapS,
632
- wrapT: m.wrapT,
633
- minFilter: m.minFilter,
634
- magFilter: m.magFilter,
635
- center: m.center,
636
- color: m.color,
637
- offset: m.offset,
638
- repeat: m.repeat,
639
- rotation: m.rotation,
640
- texCoord: m.texCoord,
641
- flipY: m.flipY
642
- }
643
- );
644
-
645
- const textureIndex = this.convertTexture(mapData);
646
- if (textureIndex !== undefined) materialDef.pbrMetallicRoughness!.metallicRoughnessTexture = { index: textureIndex };
647
- resolve();
648
- })
649
- .catch(reject);
650
- } catch (e) {
651
- reject(e);
652
- }
653
- }));
654
- } else {
655
- // no support for combining textures
656
- const textureIndex = this.convertTexture(standardMaterialData.roughnessMap);
657
- if (textureIndex !== undefined) materialDef.pbrMetallicRoughness!.metallicRoughnessTexture = { index: textureIndex };
658
- }
659
- } else if (standardMaterialData.metalnessMap && includeMaps) {
660
- const textureIndex = this.convertTexture(standardMaterialData.metalnessMap);
661
- if (textureIndex !== undefined) materialDef.pbrMetallicRoughness!.metallicRoughnessTexture = { index: textureIndex };
662
- } else if (standardMaterialData.roughnessMap && includeMaps) {
663
- const textureIndex = this.convertTexture(standardMaterialData.roughnessMap);
664
- if (textureIndex !== undefined) materialDef.pbrMetallicRoughness!.metallicRoughnessTexture = { index: textureIndex };
665
- }
666
- }
667
-
668
- if (data.normalMap && includeMaps) {
669
- const textureIndex = this.convertTexture(data.normalMap);
670
- if (textureIndex !== undefined) materialDef.normalTexture = { index: textureIndex };
671
- }
672
-
673
- if (data.aoMap && includeMaps) {
674
- const textureIndex = this.convertTexture(data.aoMap);
675
- if (textureIndex !== undefined) materialDef.occlusionTexture = { index: textureIndex };
676
- }
677
-
678
- if (data.emissiveMap && includeMaps) {
679
- const textureIndex = this.convertTexture(data.emissiveMap);
680
- if (textureIndex !== undefined) materialDef.emissiveTexture = { index: textureIndex };
681
- }
682
-
683
- if (data.emissiveness) materialDef.emissiveFactor = this._converter.toColorArray(data.emissiveness);
684
- materialDef.alphaMode = data.alphaMode.toUpperCase();
685
- if (data.alphaMode === MATERIAL_ALPHA.MASK) materialDef.alphaCutoff = data.alphaCutoff;
686
- materialDef.doubleSided = data.side === MATERIAL_SIDE.DOUBLE;
687
-
688
- this._content.materials.push(materialDef);
689
-
690
- this._materialCache[data.id + '_' + data.version] = this._content.materials.length - 1;
691
- return this._materialCache[data.id + '_' + data.version];
692
- }
693
-
694
- private convertMesh(data: IGeometryData): number {
695
- if (!this._content.meshes) this._content.meshes = [];
696
- if (this._meshCache[data.id + '_' + data.version] !== undefined) return this._meshCache[data.id + '_' + data.version];
697
-
698
- const meshDef: IGLTF_v2_Mesh = {
699
- primitives: [],
700
- name: data.id
701
- };
702
-
703
- meshDef.primitives?.push(this.convertPrimitive(data, data.primitive));
704
-
705
- this._content.meshes.push(meshDef);
706
- this._meshCache[data.id + '_' + data.version] = this._content.meshes.length - 1;
707
- return this._meshCache[data.id + '_' + data.version];
708
- }
709
-
710
- private async convertNode(node: ITreeNode): Promise<number> {
711
- if (!this._content.nodes) this._content.nodes = [];
712
- const nodeDef: IGLTF_v2_Node = {
713
- name: this._convertForAR ? this._uuidGenerator.create() : node.name,
714
- };
715
-
716
- if (node.transformations.length > 0) {
717
- let matrix = node.nodeMatrix;
718
- if (node.nodeMatrix.filter(v => isNaN(v) || v === Infinity || v === -Infinity).length > 0)
719
- matrix = mat4.create();
720
-
721
- nodeDef.matrix = [matrix[0], matrix[1], matrix[2], matrix[3],
722
- matrix[4], matrix[5], matrix[6], matrix[7],
723
- matrix[8], matrix[9], matrix[10], matrix[11],
724
- matrix[12], matrix[13], matrix[14], matrix[15]];
725
- }
726
-
727
- for (let i = 0; i < node.data.length; i++) {
728
- if (node.data[i] instanceof GeometryData) {
729
- if (this._convertForAR) {
730
- if ((<GeometryData>node.data[i]).mode !== PRIMITIVE_MODE.POINTS &&
731
- (<GeometryData>node.data[i]).mode !== PRIMITIVE_MODE.LINES &&
732
- (<GeometryData>node.data[i]).mode !== PRIMITIVE_MODE.LINE_LOOP &&
733
- (<GeometryData>node.data[i]).mode !== PRIMITIVE_MODE.LINE_STRIP)
734
- nodeDef.mesh = this.convertMesh(<GeometryData>node.data[i]);
735
- } else {
736
- nodeDef.mesh = this.convertMesh(<GeometryData>node.data[i]);
737
- }
738
- }
739
-
740
- if (node.data[i] instanceof AnimationData)
741
- this._animations.push(<AnimationData>node.data[i]);
742
- }
743
-
744
- if (node.children.length > 0) nodeDef.children = [];
745
- for (let i = 0; i < node.children.length; i++) {
746
- if (node.children[i].visible === true) {
747
- if (this._viewport) {
748
- if (node.children[i].excludeViewports.includes(this._viewport)) continue;
749
- if (node.children[i].restrictViewports.length > 0 && !node.children[i].restrictViewports.includes(this._viewport)) continue;
750
- }
751
- const nodeId = await this.convertNode(node.children[i]);
752
- if (nodeId !== -1) nodeDef.children?.push(nodeId);
753
- }
754
- }
755
-
756
- // remove children array if it is empty
757
- if (nodeDef.children !== undefined && nodeDef.children.length === 0)
758
- nodeDef.children = undefined;
759
-
760
- if (performance.now() - this._progressTimer > this._progressUpdateLimit) {
761
- this._progressTimer = performance.now();
762
- const eventProgress: ITaskEvent = { type: TASK_TYPE.GLTF_CREATION, id: this._eventId, progress: (this._content.nodes.length / this._numberOfNodes) / 2, status: `GlTF conversion progress: ${this._content.nodes.length}/${this._numberOfNodes} nodes.` };
763
- this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventProgress);
764
- await new Promise(resolve => setTimeout(resolve, 0));
765
- }
766
-
767
- // if the node is empty, don't add it
768
- if (nodeDef.camera === undefined && nodeDef.children === undefined && nodeDef.mesh === undefined && nodeDef.extensions === undefined && nodeDef.extras === undefined && nodeDef.skin === undefined) return -1;
769
-
770
- this._content.nodes.push(nodeDef);
771
- this._nodes.push({
772
- node,
773
- id: this._content.nodes.length - 1
774
- });
775
-
776
- return this._content.nodes.length - 1;
777
- }
778
-
779
- private convertPrimitive(geometryData: IGeometryData, data: IPrimitiveData): IGLTF_v2_Primitive {
780
- const primitiveDef: IGLTF_v2_Primitive = {
781
- attributes: {},
782
- mode: geometryData.mode
783
- };
784
-
785
- for (const a in data.attributes) {
786
- if (data.attributes[a].array.length > 0) {
787
- if (a.includes('COLOR')) {
788
- if (data.attributes[a].itemSize % 4 === 0) {
789
- primitiveDef.attributes[a] = this.convertAccessor(data.attributes[a]);
790
- } else if (data.attributes[a].itemSize % 3 === 0) {
791
- const oldAttributeData = data.attributes[a];
792
- const newArray = new Float32Array((oldAttributeData.array.length / 3) * 4);
793
-
794
- let counter = 0;
795
- for (let i = 0; i < newArray.length; i += 4) {
796
- newArray[i] = oldAttributeData.array[counter] / (oldAttributeData.elementBytes === 1 ? 255.0 : 1.0);
797
- newArray[i + 1] = oldAttributeData.array[counter + 1] / (oldAttributeData.elementBytes === 1 ? 255.0 : 1.0);
798
- newArray[i + 2] = oldAttributeData.array[counter + 2] / (oldAttributeData.elementBytes === 1 ? 255.0 : 1.0);
799
- newArray[i + 3] = 1.0;
800
- counter += 3;
801
- }
802
- primitiveDef.attributes[a] = this.convertAccessor(new AttributeData(newArray, 4, 4 * 4, oldAttributeData.byteOffset, 4, oldAttributeData.normalized, oldAttributeData.count, oldAttributeData.min, oldAttributeData.max, oldAttributeData.byteStride));
803
- }
804
- } else {
805
- primitiveDef.attributes[a] = this.convertAccessor(data.attributes[a]);
806
- }
807
- }
808
- }
809
-
810
- if (data.indices)
811
- primitiveDef.indices = this.convertAccessor(data.indices);
812
-
813
- if (geometryData.material) {
814
- const k = Object.keys(primitiveDef.attributes).find(k => k.includes('TEXCOORD'));
815
- primitiveDef.material = this.convertMaterial(geometryData.material, !!k);
816
- }
817
-
818
- return primitiveDef;
819
- }
820
-
821
- private convertTexture(data: IMapData): number | undefined {
822
- if (!this._content.textures) this._content.textures = [];
823
-
824
- const imageIndex = this.convertImage(data);
825
- if (imageIndex === undefined) return;
826
- const textureDef: IGLTF_v2_Texture = {
827
- source: imageIndex
828
- };
829
- // TODO samplers
830
- this._content.textures.push(textureDef);
831
- return this._content.textures.length - 1;
832
- }
833
-
834
- private getComponentType(array: Int8Array | Uint8Array | Int16Array | Uint16Array | Uint32Array | Float32Array) {
835
- switch (true) {
836
- case array instanceof Int8Array:
837
- return 5120;
838
- case array instanceof Uint8Array:
839
- return 5121;
840
- case array instanceof Int16Array:
841
- return 5122;
842
- case array instanceof Uint16Array:
843
- return 5123;
844
- case array instanceof Uint32Array:
845
- return 5125;
846
- default:
847
- return 5126;
848
- }
849
- }
850
-
851
- private getMinMax(data: IAttributeData): { min: number[], max: number[] } {
852
- const output = {
853
- min: new Array(data.itemSize).fill(Number.POSITIVE_INFINITY),
854
- max: new Array(data.itemSize).fill(Number.NEGATIVE_INFINITY)
855
- };
856
-
857
- for (let i = 0; i < data.count; i++) {
858
- for (let a = 0; a < data.itemSize; a++) {
859
- let value = 0;
860
- if (data.itemSize > 4) {
861
- // no support for interleaved data for itemSize > 4
862
- value = data.array[i * data.itemSize + a];
863
- } else {
864
- if (a === 0) value = data.array[i * data.itemSize];
865
- else if (a === 1) value = data.array[i * data.itemSize + 1];
866
- else if (a === 2) value = data.array[i * data.itemSize + 2];
867
- else if (a === 3) value = data.array[i * data.itemSize + 3];
868
- }
869
- output.min[a] = Math.min(output.min[a], value);
870
- output.max[a] = Math.max(output.max[a], value);
871
- }
872
- }
873
- return output;
874
- }
875
-
876
- private getPaddedArrayBuffer(arrayBuffer: ArrayBuffer, paddingByte = 0) {
877
- const paddedLength = Math.ceil(arrayBuffer.byteLength / 4) * 4;
878
-
879
- if (paddedLength !== arrayBuffer.byteLength) {
880
- const array = new Uint8Array(paddedLength);
881
- array.set(new Uint8Array(arrayBuffer));
882
-
883
- if (paddingByte !== 0) {
884
- for (let i = arrayBuffer.byteLength; i < paddedLength; i++) {
885
- array[i] = paddingByte;
886
- }
887
- }
888
-
889
- return array.buffer;
890
- }
891
-
892
- return arrayBuffer;
893
- }
894
-
895
- private getType(itemSize: number) {
896
- switch (itemSize) {
897
- case 1:
898
- return 'SCALAR';
899
- case 2:
900
- return 'VEC2';
901
- case 3:
902
- return 'VEC3';
903
- case 4:
904
- return 'VEC4';
905
- case 9:
906
- return 'MAT3';
907
- case 18:
908
- return 'MAT4';
909
- default:
910
- return 'VEC3';
911
- }
912
- }
913
-
914
- private reset() {
915
- this._animations = [];
916
- this._buffers = [];
917
- this._byteOffset = 0;
918
- this._content = {
919
- asset: {
920
- copyright: '2023 (c) ShapeDiver',
921
- generator: 'ShapeDiverViewer@' + build_data.build_version,
922
- version: '2.0',
923
- extensions: {}
924
- },
925
- };
926
-
927
- this._extensionsRequired = [];
928
- this._extensionsUsed = [];
929
- this._imageCache = {};
930
- this._materialCache = {};
931
- this._meshCache = {};
932
- this._nodes = [];
933
- this._promises = [];
934
-
935
- this._convertForAR = false;
936
- this._viewport = undefined;
937
- }
938
-
939
- private stringToArrayBuffer(text: string) {
940
- if (window.TextEncoder !== undefined) {
941
- return new TextEncoder().encode(text).buffer;
942
- }
943
-
944
- const array = new Uint8Array(new ArrayBuffer(text.length));
945
-
946
- for (let i = 0, il = text.length; i < il; i++) {
947
- const value = text.charCodeAt(i);
948
-
949
- // Replacing multi-byte character with space(0x20).
950
- array[i] = value > 0xFF ? 0x20 : value;
951
- }
952
-
953
- return array.buffer;
954
- }
955
-
956
- // #endregion Private Methods (17)
957
- }
958
-
959
- // #endregion Classes (1)
960
-
961
- // #region Enums (1)
962
-
963
- export enum GLTF_EXTENSIONS {
964
- KHR_BINARY_GLTF = 'KHR_binary_glTF',
965
- KHR_MATERIALS_PBRSPECULARGLOSSINESS = 'KHR_materials_pbrSpecularGlossiness',
966
- KHR_MATERIALS_UNLIT = 'KHR_materials_unlit',
967
- }
968
-
969
- // #endregion Enums (1)
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- import { GLTFConverter } from './GLTFConverter'
2
-
3
- export {
4
- GLTFConverter
5
- }
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "include": [
4
- "./**/*.ts"
5
- ],
6
- "compilerOptions": {
7
- "rootDir": "src",
8
- "outDir": "dist"
9
- },
10
- "exclude": [
11
- "__tests__",
12
- "node_modules",
13
- "dist",
14
- "dist-dev",
15
- "dist-prod"
16
- ]
17
- }