@shapediver/viewer.data-engine.geometry-engine 1.15.7 → 2.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.
Files changed (38) hide show
  1. package/dist/GeometryEngine.d.ts +2 -2
  2. package/dist/GeometryEngine.d.ts.map +1 -1
  3. package/dist/GeometryEngine.js +7 -7
  4. package/dist/GeometryEngine.js.map +1 -1
  5. package/dist/gltfv1/GLTFLoader.d.ts +3 -3
  6. package/dist/gltfv1/GLTFLoader.d.ts.map +1 -1
  7. package/dist/gltfv1/GLTFLoader.js +12 -12
  8. package/dist/gltfv1/GLTFLoader.js.map +1 -1
  9. package/dist/gltfv1/SDGTFLoader.d.ts +2 -2
  10. package/dist/gltfv1/SDGTFLoader.d.ts.map +1 -1
  11. package/dist/gltfv1/SDGTFLoader.js +15 -15
  12. package/dist/gltfv1/SDGTFLoader.js.map +1 -1
  13. package/dist/gltfv2/GLTFLoader.d.ts +3 -3
  14. package/dist/gltfv2/GLTFLoader.d.ts.map +1 -1
  15. package/dist/gltfv2/GLTFLoader.js +18 -18
  16. package/dist/gltfv2/GLTFLoader.js.map +1 -1
  17. package/dist/gltfv2/loaders/AccessorLoader.js +1 -1
  18. package/dist/gltfv2/loaders/AccessorLoader.js.map +1 -1
  19. package/dist/gltfv2/loaders/GeometryLoader.d.ts +2 -2
  20. package/dist/gltfv2/loaders/GeometryLoader.d.ts.map +1 -1
  21. package/dist/gltfv2/loaders/GeometryLoader.js.map +1 -1
  22. package/dist/gltfv2/loaders/MaterialLoader.d.ts +2 -2
  23. package/dist/gltfv2/loaders/MaterialLoader.d.ts.map +1 -1
  24. package/dist/gltfv2/loaders/MaterialLoader.js.map +1 -1
  25. package/package.json +18 -15
  26. package/src/GeometryEngine.ts +130 -0
  27. package/src/gltfv1/GLTFLoader.ts +335 -0
  28. package/src/gltfv1/SDGTFLoader.ts +830 -0
  29. package/src/gltfv2/GLTFLoader.ts +513 -0
  30. package/src/gltfv2/draco/draco_decoder.js +36 -0
  31. package/src/gltfv2/loaders/AccessorLoader.ts +139 -0
  32. package/src/gltfv2/loaders/BufferLoader.ts +77 -0
  33. package/src/gltfv2/loaders/BufferViewLoader.ts +48 -0
  34. package/src/gltfv2/loaders/GeometryLoader.ts +215 -0
  35. package/src/gltfv2/loaders/MaterialLoader.ts +348 -0
  36. package/src/gltfv2/loaders/TextureLoader.ts +88 -0
  37. package/src/index.ts +5 -0
  38. package/tsconfig.json +19 -0
@@ -0,0 +1,139 @@
1
+ import { AttributeData } from '@shapediver/viewer.shared.types'
2
+ import {
3
+ ACCESSORCOMPONENTTYPE_V2 as ACCESSOR_COMPONENTTYPE,
4
+ ACCESSORTYPE_V2 as ACCESSORTYPE,
5
+ IGLTF_v2,
6
+ IGLTF_v2_Material,
7
+ IGLTF_v2_Material_KHR_materials_pbrSpecularGlossiness,
8
+ IGLTF_v2_Primitive,
9
+ ISHAPEDIVER_materials_preset,
10
+ } from '@shapediver/viewer.data-engine.shared-types'
11
+ import { Logger, LOGGING_TOPIC } from '@shapediver/viewer.shared.services'
12
+ import { container } from 'tsyringe'
13
+
14
+ import { BufferLoader } from './BufferLoader'
15
+ import { BufferViewLoader } from './BufferViewLoader'
16
+
17
+ export class AccessorLoader {
18
+ // #region Properties (2)
19
+
20
+ private readonly _logger: Logger = <Logger>container.resolve(Logger);
21
+
22
+ private _loaded: {
23
+ [key: string]: AttributeData | null
24
+ } = {};
25
+
26
+ // #endregion Properties (2)
27
+
28
+ // #region Constructors (1)
29
+
30
+ constructor(private readonly _content: IGLTF_v2, private readonly _bufferViewLoader: BufferViewLoader) { }
31
+
32
+ // #endregion Constructors (1)
33
+
34
+ // #region Public Methods (2)
35
+
36
+ public getAccessor(accessorId: number): AttributeData | null {
37
+ if (!this._content.accessors) throw new Error('AccessorLoader.getAccessor: Accessors not available.')
38
+ if (!this._content.accessors[accessorId]) throw new Error('AccessorLoader.getAccessor: Accessor not available.')
39
+ if (!this._loaded[accessorId]) throw new Error('AccessorLoader.getAccessor: Accessor not loaded.')
40
+ return this._loaded[accessorId];
41
+ }
42
+
43
+ public load(): void {
44
+ if (!this._content.accessors) return;
45
+ for (let i = 0; i < this._content.accessors.length; i++) {
46
+ const accessorId = i;
47
+ if (!this._content.accessors[accessorId]) throw new Error('AccessorLoader.load: BufferView not available.')
48
+ const accessor = this._content.accessors[accessorId];
49
+
50
+ if (accessor.bufferView === undefined) {
51
+ // Ignore empty accessors, which may be used to declare runtime
52
+ // information about attributes coming from another source (e.g. Draco
53
+ // compression extension).
54
+ this._loaded[accessorId] = null;
55
+ continue;
56
+ }
57
+
58
+ const arrayBuffer = this._bufferViewLoader.getBufferView(accessor.bufferView!);
59
+
60
+ const itemSize = ACCESSORTYPE[<keyof typeof ACCESSORTYPE>accessor.type];
61
+ if (accessor.componentType === 5124) this._logger.warn(LOGGING_TOPIC.DATA_PROCESSING, 'GLTFLoader.loadAccessor: The componentType for this accessor is 5124, which is not allowed. Trying to load it anyway.');
62
+ const ArrayType = ACCESSOR_COMPONENTTYPE[<keyof typeof ACCESSOR_COMPONENTTYPE>accessor.componentType];
63
+
64
+ const elementBytes = ArrayType.BYTES_PER_ELEMENT;
65
+ const itemBytes = elementBytes * itemSize;
66
+ const byteOffset = accessor.byteOffset || 0;
67
+ const byteStride = accessor.bufferView !== undefined ? this._content.bufferViews ? this._content.bufferViews[accessor.bufferView].byteStride : undefined : undefined;
68
+ const normalized = accessor.normalized === true;
69
+ let array;
70
+
71
+ if (byteStride && byteStride !== itemBytes) {
72
+ // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer
73
+ // This makes sure that IBA.count reflects accessor.count properly
74
+ const ibSlice = Math.floor(byteOffset / byteStride);
75
+ array = new ArrayType(arrayBuffer, ibSlice * byteStride, accessor.count * byteStride / elementBytes);
76
+ } else {
77
+ if (arrayBuffer === null) {
78
+ array = new ArrayType(accessor.count * itemSize);
79
+ } else {
80
+ array = new ArrayType(arrayBuffer, byteOffset, accessor.count * itemSize);
81
+ }
82
+ }
83
+
84
+ if (normalized) {
85
+ const scale = this.getNormalizedComponentScale(ArrayType);
86
+ const scaled = new Float32Array(array.length);
87
+ for (let j = 0, jl = array.length; j < jl; j++)
88
+ scaled[j] = array[j] * scale;
89
+ array = scaled;
90
+ }
91
+
92
+ if (accessor.sparse !== undefined) {
93
+ const itemSizeIndices = ACCESSORTYPE.SCALAR;
94
+ const IndicesArrayType = ACCESSOR_COMPONENTTYPE[<keyof typeof ACCESSOR_COMPONENTTYPE>accessor.sparse.indices.componentType];
95
+
96
+ const byteOffsetIndices = accessor.sparse.indices.byteOffset || 0;
97
+ const byteOffsetValues = accessor.sparse.values.byteOffset || 0;
98
+
99
+ if (!accessor.sparse.indices.bufferView || !accessor.sparse.values.bufferView) throw new Error('Sparse Mesh not properly defined.')
100
+
101
+ const sparseIndices = new IndicesArrayType(this._bufferViewLoader.getBufferView(accessor.sparse.indices.bufferView!), byteOffsetIndices, accessor.sparse.count * itemSizeIndices);
102
+ const sparseValues = new ArrayType(this._bufferViewLoader.getBufferView(accessor.sparse.values.bufferView!), byteOffsetValues, accessor.sparse.count * itemSize);
103
+
104
+ this._loaded[accessorId] = new AttributeData(array, itemSize, itemBytes, byteOffset, elementBytes, normalized, accessor.count, accessor.min, accessor.max, byteStride, true, sparseIndices, sparseValues);
105
+ continue;
106
+ }
107
+
108
+ this._loaded[accessorId] = new AttributeData(array, itemSize, itemBytes, byteOffset, elementBytes, normalized, accessor.count, accessor.min, accessor.max, byteStride);
109
+ }
110
+ }
111
+
112
+ // #endregion Public Methods (2)
113
+
114
+ // #region Private Methods (1)
115
+
116
+ private getNormalizedComponentScale(constructor: Uint8ArrayConstructor | Int8ArrayConstructor | Int16ArrayConstructor | Uint16ArrayConstructor | Uint32ArrayConstructor | Float32ArrayConstructor) {
117
+ // Reference:
118
+ // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data
119
+
120
+ switch (constructor) {
121
+ case Int8Array:
122
+ return 1 / 127;
123
+
124
+ case Uint8Array:
125
+ return 1 / 255;
126
+
127
+ case Int16Array:
128
+ return 1 / 32767;
129
+
130
+ case Uint16Array:
131
+ return 1 / 65535;
132
+
133
+ default:
134
+ throw new Error('THREE.GLTFLoader: Unsupported normalized accessor component type.');
135
+ }
136
+ }
137
+
138
+ // #endregion Private Methods (1)
139
+ }
@@ -0,0 +1,77 @@
1
+ import { container } from 'tsyringe'
2
+ import { IGLTF_v2 } from '@shapediver/viewer.data-engine.shared-types'
3
+ import { HttpClient } from '@shapediver/viewer.shared.services'
4
+
5
+ export class BufferLoader {
6
+ // #region Properties (2)
7
+
8
+ private readonly _httpClient: HttpClient = <HttpClient>container.resolve(HttpClient);
9
+
10
+ private _loaded: {
11
+ [key: string]: ArrayBuffer
12
+ } = {};
13
+
14
+ // #endregion Properties (2)
15
+
16
+ // #region Constructors (1)
17
+
18
+ constructor(private readonly _content: IGLTF_v2, private _body?: ArrayBuffer, private _baseUri?: string) { }
19
+
20
+ // #endregion Constructors (1)
21
+
22
+ // #region Public Methods (2)
23
+
24
+ public getBuffer(bufferId: number): ArrayBuffer {
25
+ if (!this._content.buffers) throw new Error('BufferLoader.getBuffer: Buffers not available.')
26
+ if (!this._content.buffers[bufferId]) throw new Error('BufferLoader.getBuffer: Buffer not available.')
27
+ if (!this._loaded[bufferId]) throw new Error('BufferLoader.getBuffer: Buffer not loaded.')
28
+ return this._loaded[bufferId];
29
+ }
30
+
31
+ public async load(): Promise<void> {
32
+ if (!this._content.buffers) return;
33
+
34
+ let promises: Promise<void>[] = [];
35
+
36
+ for (let i = 0; i < this._content.buffers.length; i++) {
37
+ const bufferId = i;
38
+ const buffer = this._content.buffers[bufferId];
39
+
40
+ if (buffer.type && buffer.type !== 'arraybuffer') {
41
+ throw new Error(`BufferLoader.load: ${buffer.type} is not supported.`);
42
+ }
43
+
44
+ // If present, GLB container is required to be the first buffer.
45
+ if (buffer.uri === undefined && bufferId === 0) {
46
+ if (!this._body) throw new Error(`BufferLoader.load: Buffer not available.`);
47
+ this._loaded[bufferId] = this._body;
48
+ return;
49
+ }
50
+
51
+ const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
52
+ const dataUriRegexResult = buffer.uri!.match(dataUriRegex);
53
+
54
+ // Safari can not handle Data URIs through XMLHttpRequest so process manually
55
+ if (dataUriRegexResult) {
56
+ const isBase64 = !!dataUriRegexResult[2];
57
+ let data = dataUriRegexResult[3];
58
+ data = decodeURIComponent(data);
59
+ if (isBase64) data = atob(data);
60
+
61
+ const view = new Uint8Array(data.length);
62
+ for (let i = 0; i < data.length; i++) {
63
+ view[i] = data.charCodeAt(i);
64
+ }
65
+ this._loaded[bufferId] = view.buffer;
66
+ } else {
67
+ let httpResultPromise = this._httpClient.get(this._baseUri + '/' + buffer.uri!, {
68
+ responseType: 'arraybuffer'
69
+ }).then(response => { this._loaded[bufferId] = response.data; });
70
+ promises.push(httpResultPromise)
71
+ }
72
+ }
73
+ await Promise.all(promises);
74
+ }
75
+
76
+ // #endregion Public Methods (2)
77
+ }
@@ -0,0 +1,48 @@
1
+ import { IGLTF_v2 } from '@shapediver/viewer.data-engine.shared-types'
2
+
3
+ import { BufferLoader } from './BufferLoader'
4
+
5
+ export class BufferViewLoader {
6
+ // #region Properties (1)
7
+
8
+ private _loaded: {
9
+ [key: string]: ArrayBuffer
10
+ } = {};
11
+
12
+ // #endregion Properties (1)
13
+
14
+ // #region Constructors (1)
15
+
16
+ constructor(private readonly _content: IGLTF_v2, private readonly _bufferLoader: BufferLoader) { }
17
+
18
+ // #endregion Constructors (1)
19
+
20
+ // #region Public Methods (2)
21
+
22
+ public getBufferView(bufferViewId: number): ArrayBuffer {
23
+ if (!this._content.bufferViews) throw new Error('BufferViewLoader.load: BufferViews not available.')
24
+ if (!this._content.bufferViews[bufferViewId]) throw new Error('BufferViewLoader.load: BufferView not available.')
25
+ if (!this._loaded[bufferViewId]) throw new Error('BufferViewLoader.load: BufferView not loaded.')
26
+ return this._loaded[bufferViewId];
27
+ }
28
+
29
+ public load(): void {
30
+ if (!this._content.bufferViews) return;
31
+ for (let i = 0; i < this._content.bufferViews.length; i++) {
32
+ const bufferViewId = i;
33
+ if (!this._content.bufferViews[bufferViewId]) throw new Error('BufferViewLoader.load: BufferView not available.')
34
+ const bufferView = this._content.bufferViews[bufferViewId];
35
+
36
+ const byteLength = bufferView.byteLength || 0;
37
+ const byteOffset = bufferView.byteOffset || 0;
38
+
39
+ if (bufferView.buffer === undefined) throw new Error('BufferViewLoader.load: BufferView has no buffer defined.')
40
+ const buffer = this._bufferLoader.getBuffer(bufferView.buffer!);
41
+ const result = buffer.slice(byteOffset, byteOffset + byteLength);
42
+
43
+ this._loaded[bufferViewId] = result;
44
+ }
45
+ }
46
+
47
+ // #endregion Public Methods (2)
48
+ }
@@ -0,0 +1,215 @@
1
+ import { IGLTF_v2, IGLTF_v2_Primitive } from '@shapediver/viewer.data-engine.shared-types'
2
+ import { ITreeNode, TreeNode } from '@shapediver/viewer.shared.node-tree'
3
+ import { AttributeData, GeometryData, MaterialVariantsData, PrimitiveData } from '@shapediver/viewer.shared.types'
4
+
5
+ import { GLTF_EXTENSIONS } from '../GLTFLoader'
6
+ import { AccessorLoader } from './AccessorLoader'
7
+ import { BufferViewLoader } from './BufferViewLoader'
8
+ import { MaterialLoader } from './MaterialLoader'
9
+
10
+ export class GeometryLoader {
11
+ // #region Properties (1)
12
+
13
+ private _materialVariantsData = new MaterialVariantsData();
14
+
15
+ // #endregion Properties (1)
16
+
17
+ // #region Constructors (1)
18
+
19
+ constructor(
20
+ private readonly _content: IGLTF_v2,
21
+ private readonly _accessorLoader: AccessorLoader,
22
+ private readonly _bufferViewLoader: BufferViewLoader,
23
+ private readonly _materialLoader: MaterialLoader,
24
+ private readonly _dracoModule: any
25
+ ) {}
26
+
27
+ // #endregion Constructors (1)
28
+
29
+ // #region Public Accessors (1)
30
+
31
+ public get materialVariantsData(): MaterialVariantsData {
32
+ return this._materialVariantsData;
33
+ }
34
+
35
+ // #endregion Public Accessors (1)
36
+
37
+ // #region Public Methods (1)
38
+
39
+ public loadMesh(meshId: number, weights?: number[]): ITreeNode {
40
+ if (!this._content.meshes) throw new Error('GeometryLoader.loadMesh: Meshes not available.')
41
+ if (!this._content.meshes[meshId]) throw new Error('GeometryLoader.loadMesh: Mesh not available.')
42
+ const mesh = this._content.meshes[meshId];
43
+ const meshNode = new TreeNode(mesh.name || 'mesh_' + meshId);
44
+
45
+ if (mesh.primitives)
46
+ for (let i = 0, len = mesh.primitives.length; i < len; i++)
47
+ meshNode.addChild(this.loadPrimitive(mesh.primitives, i, mesh.weights || weights));
48
+
49
+ return meshNode;
50
+ }
51
+
52
+ // #endregion Public Methods (1)
53
+
54
+ // #region Private Methods (1)
55
+
56
+ private loadPrimitive(primitives: IGLTF_v2_Primitive[], index: number, weights: number[] = []): ITreeNode {
57
+ const primitive = primitives[index];
58
+ const primitiveNode = new TreeNode('primitive_' + index);
59
+
60
+ const attributes: {
61
+ [key: string]: AttributeData
62
+ } = {};
63
+
64
+ let indices = null;
65
+ const convertedNames: { [key: string]: string } = {}
66
+
67
+ if (primitive.extensions && primitive.extensions[GLTF_EXTENSIONS.KHR_DRACO_MESH_COMPRESSION]) {
68
+ const dracoDef = primitive.extensions[GLTF_EXTENSIONS.KHR_DRACO_MESH_COMPRESSION];
69
+ const arrayBuffer = this._bufferViewLoader.getBufferView(dracoDef.bufferView!);
70
+
71
+ const decoder = new this._dracoModule.Decoder();
72
+ const buffer = new this._dracoModule.DecoderBuffer();
73
+ buffer.Init(new Int8Array(arrayBuffer), arrayBuffer.byteLength);
74
+ const geometryType = decoder.GetEncodedGeometryType(buffer);
75
+
76
+ let dracoGeometry;
77
+ if (geometryType === this._dracoModule.TRIANGULAR_MESH) {
78
+ dracoGeometry = new this._dracoModule.Mesh();
79
+ decoder.DecodeBufferToMesh(buffer, dracoGeometry);
80
+ } else if (geometryType === this._dracoModule.POINT_CLOUD) {
81
+ dracoGeometry = new this._dracoModule.PointCloud();
82
+ decoder.DecodeBufferToPointCloud(buffer, dracoGeometry);
83
+ }
84
+ this._dracoModule.destroy(buffer);
85
+
86
+ if (dracoDef.attributes['POSITION'] === undefined) {
87
+ const errorMsg = "No position attribute found in the mesh.";
88
+ this._dracoModule.destroy(decoder);
89
+ this._dracoModule.destroy(dracoGeometry);
90
+ throw new Error(errorMsg);
91
+ }
92
+
93
+ for (let a in dracoDef.attributes) {
94
+ const attribute = decoder.GetAttributeByUniqueId(dracoGeometry, dracoDef.attributes[a])
95
+ const attributeData = new this._dracoModule.DracoFloat32Array();
96
+ decoder.GetAttributeFloatForAllPoints(dracoGeometry, attribute, attributeData);
97
+
98
+ const byteOffset = attribute.byte_offset();
99
+ const normalized = attribute.normalized();
100
+ const num_components = attribute.num_components();
101
+ const count = attributeData.size();
102
+
103
+ const array = new Float32Array(count);
104
+
105
+ for (let i = 0; i < count; i++) {
106
+ for (let a = 0; a < num_components; a++) {
107
+ const temp = i * num_components;
108
+ const value = attributeData.GetValue(temp + a);
109
+ array[temp + a] = value;
110
+ }
111
+ }
112
+ this._dracoModule.destroy(attributeData);
113
+
114
+ attributes[a] = new AttributeData(
115
+ array,
116
+ num_components, // itemSize
117
+ array.BYTES_PER_ELEMENT * num_components, // itemBytes = elementBytes * itemSize
118
+ byteOffset, // byteOffset
119
+ array.BYTES_PER_ELEMENT, // elementBytes
120
+ normalized, // normalized
121
+ array.length / num_components
122
+ );
123
+ }
124
+
125
+ const numFaces = geometryType == this._dracoModule.TRIANGULAR_MESH ? dracoGeometry.num_faces() : 0;
126
+ const numIndices = numFaces * 3;
127
+ const indexArray = new Uint32Array(numIndices);
128
+
129
+ // For mesh, we need to generate the faces.
130
+ if (geometryType == this._dracoModule.TRIANGULAR_MESH) {
131
+ const ia = new this._dracoModule.DracoInt32Array();
132
+ for (let i = 0; i < numFaces; ++i) {
133
+ decoder.GetFaceFromMesh(dracoGeometry, i, ia);
134
+ const index = i * 3;
135
+ indexArray[index] = ia.GetValue(0);
136
+ indexArray[index + 1] = ia.GetValue(1);
137
+ indexArray[index + 2] = ia.GetValue(2);
138
+ }
139
+ this._dracoModule.destroy(ia);
140
+ }
141
+ this._dracoModule.destroy(decoder);
142
+ this._dracoModule.destroy(dracoGeometry);
143
+
144
+ if (geometryType == this._dracoModule.TRIANGULAR_MESH)
145
+ indices = new AttributeData(
146
+ indexArray,
147
+ 1, // itemSize
148
+ indexArray.BYTES_PER_ELEMENT * 1, // itemBytes = elementBytes * itemSize
149
+ 0, // byteOffset
150
+ indexArray.BYTES_PER_ELEMENT, // elementBytes
151
+ false, // normalized
152
+ indexArray.length // count
153
+ );
154
+ }
155
+
156
+ for (let attribute in primitive.attributes) {
157
+ if (attributes[attribute]) {
158
+ convertedNames[attribute] = attribute;
159
+ continue;
160
+ }
161
+
162
+ let attributeName = attribute;
163
+ // attribute name conversion to be consistent with gltf
164
+ if (/\d/.test(attributeName) && !attributeName.includes('_')) {
165
+ const index = attributeName.search(/\d/)
166
+ attributeName = attributeName.substring(0, index) + '_' + attributeName.substring(index, attributeName.length);
167
+ } else if (attributeName === 'TEXCOORD' || attributeName === 'COLOR' || attributeName === 'JOINTS' || attributeName === 'WEIGHTS') {
168
+ attributeName += '_0';
169
+ } else if (attributeName === 'UV') {
170
+ attributeName = 'TEXCOORD_0';
171
+ }
172
+
173
+ convertedNames[attribute] = attributeName;
174
+ attributes[attributeName] = (this._accessorLoader.getAccessor(primitive.attributes[attribute]))!;
175
+ }
176
+
177
+ if ((primitive.indices || primitive.indices === 0) && !indices)
178
+ indices = this._accessorLoader.getAccessor(primitive.indices);
179
+
180
+ // reading and assigning morph targets
181
+ if (primitive.targets) {
182
+ for (let i = 0; i < primitive.targets.length; i++) {
183
+ for (let target in primitive.targets[i]) {
184
+ if (!attributes[target]) continue;
185
+ attributes[convertedNames[target]].morphAttributeData.push((this._accessorLoader.getAccessor(primitive.targets[i][target]))!);
186
+ }
187
+ }
188
+ }
189
+
190
+ let material = null;
191
+ if (primitive.material || primitive.material === 0)
192
+ material = this._materialLoader.getMaterial(primitive.material);
193
+
194
+ const primitiveData = new PrimitiveData(attributes, primitive.mode, indices, material);
195
+
196
+ if (primitive.extensions && primitive.extensions[GLTF_EXTENSIONS.KHR_MATERIALS_VARIANTS]) {
197
+ this._materialVariantsData.primitiveData.push(primitiveData);
198
+ const variantsExtension = primitive.extensions[GLTF_EXTENSIONS.KHR_MATERIALS_VARIANTS];
199
+
200
+ for (let i = 0; i < variantsExtension.mappings.length; i++) {
201
+ const mapping = variantsExtension.mappings[i];
202
+ const material = this._materialLoader.getMaterial(mapping.material);
203
+ for (let j = 0; j < mapping.variants.length; j++)
204
+ primitiveData.materialVariants.push({ variant: mapping.variants[j], material });
205
+ }
206
+ }
207
+
208
+ const geometryData = new GeometryData(primitiveData);
209
+ geometryData.morphWeights = weights;
210
+ primitiveNode.data.push(geometryData);
211
+ return primitiveNode;
212
+ }
213
+
214
+ // #endregion Private Methods (1)
215
+ }