PageSourceSearch

https://alexgrosek.com/three/GLTFLoader.js

js alexgrosek.com collected 2026-10-08 21:36:57 UTC 100,340 bytes, 4,311 lines download raw bytes

1import {
2	AnimationClip,
3	Bone,
4	Box3,
5	BufferAttribute,
6	BufferGeometry,
7	ClampToEdgeWrapping,
8	Color,
9	DirectionalLight,
10	DoubleSide,
11	FileLoader,
12	FrontSide,
13	Group,
14	ImageBitmapLoader,
15	InstancedMesh,
16	InterleavedBuffer,
17	InterleavedBufferAttribute,
18	Interpolant,
19	InterpolateDiscrete,
20	InterpolateLinear,
21	Line,
22	LineBasicMaterial,
23	LineLoop,
24	LineSegments,
25	LinearFilter,
26	LinearMipmapLinearFilter,
27	LinearMipmapNearestFilter,
28	Loader,
29	LoaderUtils,
30	Material,
31	MathUtils,
32	Matrix4,
33	Mesh,
34	MeshBasicMaterial,
35	MeshPhysicalMaterial,
36	MeshStandardMaterial,
37	MirroredRepeatWrapping,
38	NearestFilter,
39	NearestMipmapLinearFilter,
40	NearestMipmapNearestFilter,
41	NumberKeyframeTrack,
42	Object3D,
43	OrthographicCamera,
44	PerspectiveCamera,
45	PointLight,
46	Points,
47	PointsMaterial,
48	PropertyBinding,
49	Quaternion,
50	QuaternionKeyframeTrack,
51	RepeatWrapping,
52	Skeleton,
53	SkinnedMesh,
54	Sphere,
55	SpotLight,
56	Texture,
57	TextureLoader,
58	TriangleFanDrawMode,
59	TriangleStripDrawMode,
60	Vector2,
61	Vector3,
62	VectorKeyframeTrack,
63	sRGBEncoding
64} from './three.module.js';
65// import { toTrianglesDrawMode } from '../utils/BufferGeometryUtils.js';
66
67class GLTFLoader extends Loader {
68
69	constructor( manager ) {
70
71		super( manager );
72
73		this.dracoLoader = null;
74		this.ktx2Loader = null;
75		this.meshoptDecoder = null;
76
77		this.pluginCallbacks = [];
78
79		this.register( function ( parser ) {
80
81			return new GLTFMaterialsClearcoatExtension( parser );
82
83		} );
84
85		this.register( function ( parser ) {
86
87			return new GLTFTextureBasisUExtension( parser );
88
89		} );
90
91		this.register( function ( parser ) {
92
93			return new GLTFTextureWebPExtension( parser );
94
95		} );
96
97		this.register( function ( parser ) {
98
99			return new GLTFMaterialsSheenExtension( parser );
100
101		} );
102
103		this.register( function ( parser ) {
104
105			return new GLTFMaterialsTransmissionExtension( parser );
106
107		} );
108
109		this.register( function ( parser ) {
110
111			return new GLTFMaterialsVolumeExtension( parser );
112
113		} );
114
115		this.register( function ( parser ) {
116
vendor: 10,036 bytes, lines 117-584
117			return new GLTFMaterialsIorExtension( parser );
118
119		} );
120
121		this.register( function ( parser ) {
122
123			return new GLTFMaterialsEmissiveStrengthExtension( parser );
124
125		} );
126
127		this.register( function ( parser ) {
128
129			return new GLTFMaterialsSpecularExtension( parser );
130
131		} );
132
133		this.register( function ( parser ) {
134
135			return new GLTFMaterialsIridescenceExtension( parser );
136
137		} );
138
139		this.register( function ( parser ) {
140
141			return new GLTFLightsExtension( parser );
142
143		} );
144
145		this.register( function ( parser ) {
146
147			return new GLTFMeshoptCompression( parser );
148
149		} );
150
151		this.register( function ( parser ) {
152
153			return new GLTFMeshGpuInstancing( parser );
154
155		} );
156
157	}
158
159	load( url, onLoad, onProgress, onError ) {
160
161		const scope = this;
162
163		let resourcePath;
164
165		if ( this.resourcePath !== '' ) {
166
167			resourcePath = this.resourcePath;
168
169		} else if ( this.path !== '' ) {
170
171			resourcePath = this.path;
172
173		} else {
174
175			resourcePath = LoaderUtils.extractUrlBase( url );
176
177		}
178
179		// Tells the LoadingManager to track an extra item, which resolves after
180		// the model is fully loaded. This means the count of items loaded will
181		// be incorrect, but ensures manager.onLoad() does not fire early.
182		this.manager.itemStart( url );
183
184		const _onError = function ( e ) {
185
186			if ( onError ) {
187
188				onError( e );
189
190			} else {
191
192				console.error( e );
193
194			}
195
196			scope.manager.itemError( url );
197			scope.manager.itemEnd( url );
198
199		};
200
201		const loader = new FileLoader( this.manager );
202
203		loader.setPath( this.path );
204		loader.setResponseType( 'arraybuffer' );
205		loader.setRequestHeader( this.requestHeader );
206		loader.setWithCredentials( this.withCredentials );
207
208		loader.load( url, function ( data ) {
209
210			try {
211
212				scope.parse( data, resourcePath, function ( gltf ) {
213
214					onLoad( gltf );
215
216					scope.manager.itemEnd( url );
217
218				}, _onError );
219
220			} catch ( e ) {
221
222				_onError( e );
223
224			}
225
226		}, onProgress, _onError );
227
228	}
229
230	setDRACOLoader( dracoLoader ) {
231
232		this.dracoLoader = dracoLoader;
233		return this;
234
235	}
236
237	setDDSLoader() {
238
239		throw new Error(
240
241			'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".'
242
243		);
244
245	}
246
247	setKTX2Loader( ktx2Loader ) {
248
249		this.ktx2Loader = ktx2Loader;
250		return this;
251
252	}
253
254	setMeshoptDecoder( meshoptDecoder ) {
255
256		this.meshoptDecoder = meshoptDecoder;
257		return this;
258
259	}
260
261	register( callback ) {
262
263		if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) {
264
265			this.pluginCallbacks.push( callback );
266
267		}
268
269		return this;
270
271	}
272
273	unregister( callback ) {
274
275		if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) {
276
277			this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 );
278
279		}
280
281		return this;
282
283	}
284
285	parse( data, path, onLoad, onError ) {
286
287		let json;
288		const extensions = {};
289		const plugins = {};
290		const textDecoder = new TextDecoder();
291
292		if ( typeof data === 'string' ) {
293
294			json = JSON.parse( data );
295
296		} else if ( data instanceof ArrayBuffer ) {
297
298			const magic = textDecoder.decode( new Uint8Array( data, 0, 4 ) );
299
300			if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {
301
302				try {
303
304					extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );
305
306				} catch ( error ) {
307
308					if ( onError ) onError( error );
309					return;
310
311				}
312
313				json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content );
314
315			} else {
316
317				json = JSON.parse( textDecoder.decode( data ) );
318
319			}
320
321		} else {
322
323			json = data;
324
325		}
326
327		if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
328
329			if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) );
330			return;
331
332		}
333
334		const parser = new GLTFParser( json, {
335
336			path: path || this.resourcePath || '',
337			crossOrigin: this.crossOrigin,
338			requestHeader: this.requestHeader,
339			manager: this.manager,
340			ktx2Loader: this.ktx2Loader,
341			meshoptDecoder: this.meshoptDecoder
342
343		} );
344
345		parser.fileLoader.setRequestHeader( this.requestHeader );
346
347		for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) {
348
349			const plugin = this.pluginCallbacks[ i ]( parser );
350			plugins[ plugin.name ] = plugin;
351
352			// Workaround to avoid determining as unknown extension
353			// in addUnknownExtensionsToUserData().
354			// Remove this workaround if we move all the existing
355			// extension handlers to plugin system
356			extensions[ plugin.name ] = true;
357
358		}
359
360		if ( json.extensionsUsed ) {
361
362			for ( let i = 0; i < json.extensionsUsed.length; ++ i ) {
363
364				const extensionName = json.extensionsUsed[ i ];
365				const extensionsRequired = json.extensionsRequired || [];
366
367				switch ( extensionName ) {
368
369					case EXTENSIONS.KHR_MATERIALS_UNLIT:
370						extensions[ extensionName ] = new GLTFMaterialsUnlitExtension();
371						break;
372
373					case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
374						extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
375						break;
376
377					case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
378						extensions[ extensionName ] = new GLTFTextureTransformExtension();
379						break;
380
381					case EXTENSIONS.KHR_MESH_QUANTIZATION:
382						extensions[ extensionName ] = new GLTFMeshQuantizationExtension();
383						break;
384
385					default:
386
387						if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) {
388
389							console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' );
390
391						}
392
393				}
394
395			}
396
397		}
398
399		parser.setExtensions( extensions );
400		parser.setPlugins( plugins );
401		parser.parse( onLoad, onError );
402
403	}
404
405	parseAsync( data, path ) {
406
407		const scope = this;
408
409		return new Promise( function ( resolve, reject ) {
410
411			scope.parse( data, path, resolve, reject );
412
413		} );
414
415	}
416
417}
418
419/* GLTFREGISTRY */
420
421function GLTFRegistry() {
422
423	let objects = {};
424
425	return	{
426
427		get: function ( key ) {
428
429			return objects[ key ];
430
431		},
432
433		add: function ( key, object ) {
434
435			objects[ key ] = object;
436
437		},
438
439		remove: function ( key ) {
440
441			delete objects[ key ];
442
443		},
444
445		removeAll: function () {
446
447			objects = {};
448
449		}
450
451	};
452
453}
454
455/*********************************/
456/********** EXTENSIONS ***********/
457/*********************************/
458
459const EXTENSIONS = {
460	KHR_BINARY_GLTF: 'KHR_binary_glTF',
461	KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
462	KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
463	KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat',
464	KHR_MATERIALS_IOR: 'KHR_materials_ior',
465	KHR_MATERIALS_SHEEN: 'KHR_materials_sheen',
466	KHR_MATERIALS_SPECULAR: 'KHR_materials_specular',
467	KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission',
468	KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence',
469	KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
470	KHR_MATERIALS_VOLUME: 'KHR_materials_volume',
471	KHR_TEXTURE_BASISU: 'KHR_texture_basisu',
472	KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
473	KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
474	KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength',
475	EXT_TEXTURE_WEBP: 'EXT_texture_webp',
476	EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression',
477	EXT_MESH_GPU_INSTANCING: 'EXT_mesh_gpu_instancing'
478};
479
480/**
481 * Punctual Lights Extension
482 *
483 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
484 */
485class GLTFLightsExtension {
486
487	constructor( parser ) {
488
489		this.parser = parser;
490		this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
491
492		// Object3D instance caches
493		this.cache = { refs: {}, uses: {} };
494
495	}
496
497	_markDefs() {
498
499		const parser = this.parser;
500		const nodeDefs = this.parser.json.nodes || [];
501
502		for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
503
504			const nodeDef = nodeDefs[ nodeIndex ];
505
506			if ( nodeDef.extensions
507					&& nodeDef.extensions[ this.name ]
508					&& nodeDef.extensions[ this.name ].light !== undefined ) {
509
510				parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light );
511
512			}
513
514		}
515
516	}
517
518	_loadLight( lightIndex ) {
519
520		const parser = this.parser;
521		const cacheKey = 'light:' + lightIndex;
522		let dependency = parser.cache.get( cacheKey );
523
524		if ( dependency ) return dependency;
525
526		const json = parser.json;
527		const extensions = ( json.extensions && json.extensions[ this.name ] ) || {};
528		const lightDefs = extensions.lights || [];
529		const lightDef = lightDefs[ lightIndex ];
530		let lightNode;
531
532		const color = new Color( 0xffffff );
533
534		if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
535
536		const range = lightDef.range !== undefined ? lightDef.range : 0;
537
538		switch ( lightDef.type ) {
539
540			case 'directional':
541				lightNode = new DirectionalLight( color );
542				lightNode.target.position.set( 0, 0, - 1 );
543				lightNode.add( lightNode.target );
544				break;
545
546			case 'point':
547				lightNode = new PointLight( color );
548				lightNode.distance = range;
549				break;
550
551			case 'spot':
552				lightNode = new SpotLight( color );
553				lightNode.distance = range;
554				// Handle spotlight properties.
555				lightDef.spot = lightDef.spot || {};
556				lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
557				lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
558				lightNode.angle = lightDef.spot.outerConeAngle;
559				lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
560				lightNode.target.position.set( 0, 0, - 1 );
561				lightNode.add( lightNode.target );
562				break;
563
564			default:
565				throw new Error( 'THREE.GLTFLoader: Unexpected light type: ' + lightDef.type );
566
567		}
568
569		// Some lights (e.g. spot) default to a position other than the origin. Reset the position
570		// here, because node-level parsing will only override position if explicitly specified.
571		lightNode.position.set( 0, 0, 0 );
572
573		lightNode.decay = 2;
574
575		assignExtrasToUserData( lightNode, lightDef );
576
577		if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;
578
579		lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) );
580
581		dependency = Promise.resolve( lightNode );
582
583		parser.cache.add( cacheKey, dependency );
584
585		return dependency;
586
587	}
588
589	getDependency( type, index ) {
590
591		if ( type !== 'light' ) return;
592
593		return this._loadLight( index );
594
595	}
596
597	createNodeAttachment( nodeIndex ) {
598
599		const self = this;
600		const parser = this.parser;
601		const json = parser.json;
602		const nodeDef = json.nodes[ nodeIndex ];
603		const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {};
604		const lightIndex = lightDef.light;
605
606		if ( lightIndex === undefined ) return null;
607
608		return this._loadLight( lightIndex ).then( function ( light ) {
609
610			return parser._getNodeRef( self.cache, lightIndex, light );
611
612		} );
613
614	}
615
616}
617
618/**
619 * Unlit Materials Extension
620 *
621 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
622 */
623class GLTFMaterialsUnlitExtension {
624
625	constructor() {
626
627		this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
628
629	}
630
631	getMaterialType() {
632
633		return MeshBasicMaterial;
634
635	}
636
637	extendParams( materialParams, materialDef, parser ) {
638
639		const pending = [];
640
641		materialParams.color = new Color( 1.0, 1.0, 1.0 );
642		materialParams.opacity = 1.0;
643
644		const metallicRoughness = materialDef.pbrMetallicRoughness;
645
646		if ( metallicRoughness ) {
647
648			if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
649
650				const array = metallicRoughness.baseColorFactor;
651
652				materialParams.color.fromArray( array );
653				materialParams.opacity = array[ 3 ];
654
655			}
656
657			if ( metallicRoughness.baseColorTexture !== undefined ) {
658
659				pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, sRGBEncoding ) );
660
661			}
662
663		}
664
665		return Promise.all( pending );
666
667	}
668
669}
670
671/**
672 * Materials Emissive Strength Extension
673 *
674 * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md
675 */
676class GLTFMaterialsEmissiveStrengthExtension {
677
678	constructor( parser ) {
679
680		this.parser = parser;
681		this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH;
682
683	}
684
685	extendMaterialParams( materialIndex, materialParams ) {
686
687		const parser = this.parser;
688		const materialDef = parser.json.materials[ materialIndex ];
689
690		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
691
692			return Promise.resolve();
693
694		}
695
696		const emissiveStrength = materialDef.extensions[ this.name ].emissiveStrength;
697
698		if ( emissiveStrength !== undefined ) {
699
700			materialParams.emissiveIntensity = emissiveStrength;
701
702		}
703
704		return Promise.resolve();
705
706	}
707
708}
709
710/**
711 * Clearcoat Materials Extension
712 *
713 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat
714 */
715class GLTFMaterialsClearcoatExtension {
716
717	constructor( parser ) {
718
719		this.parser = parser;
720		this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT;
721
722	}
723
724	getMaterialType( materialIndex ) {
725
726		const parser = this.parser;
727		const materialDef = parser.json.materials[ materialIndex ];
728
729		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
730
731		return MeshPhysicalMaterial;
732
733	}
734
735	extendMaterialParams( materialIndex, materialParams ) {
736
737		const parser = this.parser;
738		const materialDef = parser.json.materials[ materialIndex ];
739
740		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
741
742			return Promise.resolve();
743
744		}
745
746		const pending = [];
747
748		const extension = materialDef.extensions[ this.name ];
749
750		if ( extension.clearcoatFactor !== undefined ) {
751
752			materialParams.clearcoat = extension.clearcoatFactor;
753
754		}
755
756		if ( extension.clearcoatTexture !== undefined ) {
757
758			pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) );
759
760		}
761
762		if ( extension.clearcoatRoughnessFactor !== undefined ) {
763
764			materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor;
765
766		}
767
768		if ( extension.clearcoatRoughnessTexture !== undefined ) {
769
770			pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) );
771
772		}
773
774		if ( extension.clearcoatNormalTexture !== undefined ) {
775
776			pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) );
777
778			if ( extension.clearcoatNormalTexture.scale !== undefined ) {
779
780				const scale = extension.clearcoatNormalTexture.scale;
781
782				materialParams.clearcoatNormalScale = new Vector2( scale, scale );
783
784			}
785
786		}
787
788		return Promise.all( pending );
789
790	}
791
792}
793
794/**
795 * Iridescence Materials Extension
796 *
797 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence
798 */
799class GLTFMaterialsIridescenceExtension {
800
801	constructor( parser ) {
802
803		this.parser = parser;
804		this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE;
805
806	}
807
808	getMaterialType( materialIndex ) {
809
810		const parser = this.parser;
811		const materialDef = parser.json.materials[ materialIndex ];
812
813		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
814
815		return MeshPhysicalMaterial;
816
817	}
818
819	extendMaterialParams( materialIndex, materialParams ) {
820
821		const parser = this.parser;
822		const materialDef = parser.json.materials[ materialIndex ];
823
824		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
825
826			return Promise.resolve();
827
828		}
829
830		const pending = [];
831
832		const extension = materialDef.extensions[ this.name ];
833
834		if ( extension.iridescenceFactor !== undefined ) {
835
836			materialParams.iridescence = extension.iridescenceFactor;
837
838		}
839
840		if ( extension.iridescenceTexture !== undefined ) {
841
842			pending.push( parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) );
843
844		}
845
846		if ( extension.iridescenceIor !== undefined ) {
847
848			materialParams.iridescenceIOR = extension.iridescenceIor;
849
850		}
851
852		if ( materialParams.iridescenceThicknessRange === undefined ) {
853
854			materialParams.iridescenceThicknessRange = [ 100, 400 ];
855
856		}
857
858		if ( extension.iridescenceThicknessMinimum !== undefined ) {
859
860			materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum;
861
862		}
863
864		if ( extension.iridescenceThicknessMaximum !== undefined ) {
865
866			materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum;
867
868		}
869
870		if ( extension.iridescenceThicknessTexture !== undefined ) {
871
872			pending.push( parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) );
873
874		}
875
876		return Promise.all( pending );
877
878	}
879
880}
881
882/**
883 * Sheen Materials Extension
884 *
885 * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen
886 */
887class GLTFMaterialsSheenExtension {
888
889	constructor( parser ) {
890
891		this.parser = parser;
892		this.name = EXTENSIONS.KHR_MATERIALS_SHEEN;
893
894	}
895
896	getMaterialType( materialIndex ) {
897
898		const parser = this.parser;
899		const materialDef = parser.json.materials[ materialIndex ];
900
901		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
902
903		return MeshPhysicalMaterial;
904
905	}
906
907	extendMaterialParams( materialIndex, materialParams ) {
908
909		const parser = this.parser;
910		const materialDef = parser.json.materials[ materialIndex ];
911
912		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
913
914			return Promise.resolve();
915
916		}
917
918		const pending = [];
919
920		materialParams.sheenColor = new Color( 0, 0, 0 );
921		materialParams.sheenRoughness = 0;
922		materialParams.sheen = 1;
923
924		const extension = materialDef.extensions[ this.name ];
925
926		if ( extension.sheenColorFactor !== undefined ) {
927
928			materialParams.sheenColor.fromArray( extension.sheenColorFactor );
929
930		}
931
932		if ( extension.sheenRoughnessFactor !== undefined ) {
933
934			materialParams.sheenRoughness = extension.sheenRoughnessFactor;
935
936		}
937
938		if ( extension.sheenColorTexture !== undefined ) {
939
940			pending.push( parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, sRGBEncoding ) );
941
942		}
943
944		if ( extension.sheenRoughnessTexture !== undefined ) {
945
946			pending.push( parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) );
947
948		}
949
950		return Promise.all( pending );
951
952	}
953
954}
955
956/**
957 * Transmission Materials Extension
958 *
959 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission
960 * Draft: https://github.com/KhronosGroup/glTF/pull/1698
961 */
962class GLTFMaterialsTransmissionExtension {
963
964	constructor( parser ) {
965
966		this.parser = parser;
967		this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION;
968
969	}
970
971	getMaterialType( materialIndex ) {
972
973		const parser = this.parser;
974		const materialDef = parser.json.materials[ materialIndex ];
975
976		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
977
978		return MeshPhysicalMaterial;
979
980	}
981
982	extendMaterialParams( materialIndex, materialParams ) {
983
984		const parser = this.parser;
985		const materialDef = parser.json.materials[ materialIndex ];
986
987		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
988
989			return Promise.resolve();
990
991		}
992
993		const pending = [];
994
995		const extension = materialDef.extensions[ this.name ];
996
997		if ( extension.transmissionFactor !== undefined ) {
998
999			materialParams.transmission = extension.transmissionFactor;
1000
1001		}
1002
1003		if ( extension.transmissionTexture !== undefined ) {
1004
1005			pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) );
1006
1007		}
1008
1009		return Promise.all( pending );
1010
1011	}
1012
1013}
1014
1015/**
1016 * Materials Volume Extension
1017 *
1018 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume
1019 */
1020class GLTFMaterialsVolumeExtension {
1021
1022	constructor( parser ) {
1023
1024		this.parser = parser;
1025		this.name = EXTENSIONS.KHR_MATERIALS_VOLUME;
1026
1027	}
1028
1029	getMaterialType( materialIndex ) {
1030
1031		const parser = this.parser;
1032		const materialDef = parser.json.materials[ materialIndex ];
1033
1034		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
1035
1036		return MeshPhysicalMaterial;
1037
1038	}
1039
1040	extendMaterialParams( materialIndex, materialParams ) {
1041
1042		const parser = this.parser;
1043		const materialDef = parser.json.materials[ materialIndex ];
1044
1045		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
1046
1047			return Promise.resolve();
1048
1049		}
1050
1051		const pending = [];
1052
1053		const extension = materialDef.extensions[ this.name ];
1054
1055		materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0;
1056
1057		if ( extension.thicknessTexture !== undefined ) {
1058
1059			pending.push( parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) );
1060
1061		}
1062
1063		materialParams.attenuationDistance = extension.attenuationDistance || Infinity;
1064
1065		const colorArray = extension.attenuationColor || [ 1, 1, 1 ];
1066		materialParams.attenuationColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] );
1067
1068		return Promise.all( pending );
1069
1070	}
1071
1072}
1073
1074/**
1075 * Materials ior Extension
1076 *
1077 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior
1078 */
1079class GLTFMaterialsIorExtension {
1080
1081	constructor( parser ) {
1082
1083		this.parser = parser;
1084		this.name = EXTENSIONS.KHR_MATERIALS_IOR;
1085
1086	}
1087
1088	getMaterialType( materialIndex ) {
1089
1090		const parser = this.parser;
1091		const materialDef = parser.json.materials[ materialIndex ];
1092
1093		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
1094
1095		return MeshPhysicalMaterial;
1096
1097	}
1098
1099	extendMaterialParams( materialIndex, materialParams ) {
1100
1101		const parser = this.parser;
1102		const materialDef = parser.json.materials[ materialIndex ];
1103
1104		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
1105
1106			return Promise.resolve();
1107
1108		}
1109
1110		const extension = materialDef.extensions[ this.name ];
1111
1112		materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5;
1113
1114		return Promise.resolve();
1115
1116	}
1117
1118}
1119
1120/**
1121 * Materials specular Extension
1122 *
1123 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular
1124 */
1125class GLTFMaterialsSpecularExtension {
1126
1127	constructor( parser ) {
1128
1129		this.parser = parser;
1130		this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR;
1131
1132	}
1133
1134	getMaterialType( materialIndex ) {
1135
1136		const parser = this.parser;
1137		const materialDef = parser.json.materials[ materialIndex ];
1138
1139		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
1140
1141		return MeshPhysicalMaterial;
1142
1143	}
1144
1145	extendMaterialParams( materialIndex, materialParams ) {
1146
1147		const parser = this.parser;
1148		const materialDef = parser.json.materials[ materialIndex ];
1149
1150		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
1151
1152			return Promise.resolve();
1153
1154		}
1155
1156		const pending = [];
1157
1158		const extension = materialDef.extensions[ this.name ];
1159
1160		materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0;
1161
1162		if ( extension.specularTexture !== undefined ) {
1163
1164			pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) );
1165
1166		}
1167
1168		const colorArray = extension.specularColorFactor || [ 1, 1, 1 ];
1169		materialParams.specularColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] );
1170
1171		if ( extension.specularColorTexture !== undefined ) {
1172
1173			pending.push( parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, sRGBEncoding ) );
1174
1175		}
1176
1177		return Promise.all( pending );
1178
1179	}
1180
1181}
1182
1183/**
1184 * BasisU Texture Extension
1185 *
1186 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu
1187 */
1188class GLTFTextureBasisUExtension {
1189
1190	constructor( parser ) {
1191
1192		this.parser = parser;
1193		this.name = EXTENSIONS.KHR_TEXTURE_BASISU;
1194
1195	}
1196
1197	loadTexture( textureIndex ) {
1198
1199		const parser = this.parser;
1200		const json = parser.json;
1201
1202		const textureDef = json.textures[ textureIndex ];
1203
1204		if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) {
1205
1206			return null;
1207
1208		}
1209
1210		const extension = textureDef.extensions[ this.name ];
1211		const loader = parser.options.ktx2Loader;
1212
1213		if ( ! loader ) {
1214
1215			if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) {
1216
1217				throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' );
1218
1219			} else {
1220
1221				// Assumes that the extension is optional and that a fallback texture is present
1222				return null;
1223
1224			}
1225
1226		}
1227
1228		return parser.loadTextureImage( textureIndex, extension.source, loader );
1229
1230	}
1231
1232}
1233
1234/**
1235 * WebP Texture Extension
1236 *
1237 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp
1238 */
1239class GLTFTextureWebPExtension {
1240
1241	constructor( parser ) {
1242
1243		this.parser = parser;
1244		this.name = EXTENSIONS.EXT_TEXTURE_WEBP;
1245		this.isSupported = null;
1246
1247	}
1248
1249	loadTexture( textureIndex ) {
1250
1251		const name = this.name;
1252		const parser = this.parser;
1253		const json = parser.json;
1254
1255		const textureDef = json.textures[ textureIndex ];
1256
1257		if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) {
1258
1259			return null;
1260
1261		}
1262
1263		const extension = textureDef.extensions[ name ];
1264		const source = json.images[ extension.source ];
1265
1266		let loader = parser.textureLoader;
1267		if ( source.uri ) {
1268
1269			const handler = parser.options.manager.getHandler( source.uri );
1270			if ( handler !== null ) loader = handler;
1271
1272		}
1273
1274		return this.detectSupport().then( function ( isSupported ) {
1275
1276			if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader );
1277
1278			if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) {
1279
1280				throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' );
1281
1282			}
1283
1284			// Fall back to PNG or JPEG.
1285			return parser.loadTexture( textureIndex );
1286
1287		} );
1288
1289	}
1290
1291	detectSupport() {
1292
1293		if ( ! this.isSupported ) {
1294
1295			this.isSupported = new Promise( function ( resolve ) {
1296
1297				const image = new Image();
1298
1299				// Lossy test image. Support for lossy images doesn't guarantee support for all
1300				// WebP images, unfortunately.
1301				image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA';
1302
1303				image.onload = image.onerror = function () {
1304
1305					resolve( image.height === 1 );
1306
1307				};
1308
1309			} );
1310
1311		}
1312
1313		return this.isSupported;
1314
1315	}
1316
1317}
1318
1319/**
1320 * meshopt BufferView Compression Extension
1321 *
1322 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression
1323 */
1324class GLTFMeshoptCompression {
1325
1326	constructor( parser ) {
1327
1328		this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION;
1329		this.parser = parser;
1330
1331	}
1332
1333	loadBufferView( index ) {
1334
1335		const json = this.parser.json;
1336		const bufferView = json.bufferViews[ index ];
1337
1338		if ( bufferView.extensions && bufferView.extensions[ this.name ] ) {
1339
1340			const extensionDef = bufferView.extensions[ this.name ];
1341
1342			const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer );
1343			const decoder = this.parser.options.meshoptDecoder;
1344
1345			if ( ! decoder || ! decoder.supported ) {
1346
1347				if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) {
1348
1349					throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' );
1350
1351				} else {
1352
1353					// Assumes that the extension is optional and that fallback buffer data is present
1354					return null;
1355
1356				}
1357
1358			}
1359
1360			return buffer.then( function ( res ) {
1361
1362				const byteOffset = extensionDef.byteOffset || 0;
1363				const byteLength = extensionDef.byteLength || 0;
1364
1365				const count = extensionDef.count;
1366				const stride = extensionDef.byteStride;
1367
1368				const source = new Uint8Array( res, byteOffset, byteLength );
1369
1370				if ( decoder.decodeGltfBufferAsync ) {
1371
1372					return decoder.decodeGltfBufferAsync( count, stride, source, extensionDef.mode, extensionDef.filter ).then( function ( res ) {
1373
1374						return res.buffer;
1375
1376					} );
1377
1378				} else {
1379
vendor: 4,719 bytes, lines 1380-1594
1380					// Support for MeshoptDecoder 0.18 or earlier, without decodeGltfBufferAsync
1381					return decoder.ready.then( function () {
1382
1383						const result = new ArrayBuffer( count * stride );
1384						decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter );
1385						return result;
1386
1387					} );
1388
1389				}
1390
1391			} );
1392
1393		} else {
1394
1395			return null;
1396
1397		}
1398
1399	}
1400
1401}
1402
1403/**
1404 * GPU Instancing Extension
1405 *
1406 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing
1407 *
1408 */
1409class GLTFMeshGpuInstancing {
1410
1411	constructor( parser ) {
1412
1413		this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING;
1414		this.parser = parser;
1415
1416	}
1417
1418	createNodeMesh( nodeIndex ) {
1419
1420		const json = this.parser.json;
1421		const nodeDef = json.nodes[ nodeIndex ];
1422
1423		if ( ! nodeDef.extensions || ! nodeDef.extensions[ this.name ] ||
1424			nodeDef.mesh === undefined ) {
1425
1426			return null;
1427
1428		}
1429
1430		const meshDef = json.meshes[ nodeDef.mesh ];
1431
1432		// No Points or Lines + Instancing support yet
1433
1434		for ( const primitive of meshDef.primitives ) {
1435
1436			if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES &&
1437				 primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP &&
1438				 primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN &&
1439				 primitive.mode !== undefined ) {
1440
1441				return null;
1442
1443			}
1444
1445		}
1446
1447		const extensionDef = nodeDef.extensions[ this.name ];
1448		const attributesDef = extensionDef.attributes;
1449
1450		// @TODO: Can we support InstancedMesh + SkinnedMesh?
1451
1452		const pending = [];
1453		const attributes = {};
1454
1455		for ( const key in attributesDef ) {
1456
1457			pending.push( this.parser.getDependency( 'accessor', attributesDef[ key ] ).then( accessor => {
1458
1459				attributes[ key ] = accessor;
1460				return attributes[ key ];
1461
1462			} ) );
1463
1464		}
1465
1466		if ( pending.length < 1 ) {
1467
1468			return null;
1469
1470		}
1471
1472		pending.push( this.parser.createNodeMesh( nodeIndex ) );
1473
1474		return Promise.all( pending ).then( results => {
1475
1476			const nodeObject = results.pop();
1477			const meshes = nodeObject.isGroup ? nodeObject.children : [ nodeObject ];
1478			const count = results[ 0 ].count; // All attribute counts should be same
1479			const instancedMeshes = [];
1480
1481			for ( const mesh of meshes ) {
1482
1483				// Temporal variables
1484				const m = new Matrix4();
1485				const p = new Vector3();
1486				const q = new Quaternion();
1487				const s = new Vector3( 1, 1, 1 );
1488
1489				const instancedMesh = new InstancedMesh( mesh.geometry, mesh.material, count );
1490
1491				for ( let i = 0; i < count; i ++ ) {
1492
1493					if ( attributes.TRANSLATION ) {
1494
1495						p.fromBufferAttribute( attributes.TRANSLATION, i );
1496
1497					}
1498
1499					if ( attributes.ROTATION ) {
1500
1501						q.fromBufferAttribute( attributes.ROTATION, i );
1502
1503					}
1504
1505					if ( attributes.SCALE ) {
1506
1507						s.fromBufferAttribute( attributes.SCALE, i );
1508
1509					}
1510
1511					instancedMesh.setMatrixAt( i, m.compose( p, q, s ) );
1512
1513				}
1514
1515				// Add instance attributes to the geometry, excluding TRS.
1516				for ( const attributeName in attributes ) {
1517
1518					if ( attributeName !== 'TRANSLATION' &&
1519						 attributeName !== 'ROTATION' &&
1520						 attributeName !== 'SCALE' ) {
1521
1522						mesh.geometry.setAttribute( attributeName, attributes[ attributeName ] );
1523
1524					}
1525
1526				}
1527
1528				// Just in case
1529				Object3D.prototype.copy.call( instancedMesh, mesh );
1530
1531				// https://github.com/mrdoob/three.js/issues/18334
1532				instancedMesh.frustumCulled = false;
1533				this.parser.assignFinalMaterial( instancedMesh );
1534
1535				instancedMeshes.push( instancedMesh );
1536
1537			}
1538
1539			if ( nodeObject.isGroup ) {
1540
1541				nodeObject.clear();
1542
1543				nodeObject.add( ... instancedMeshes );
1544
1545				return nodeObject;
1546
1547			}
1548
1549			return instancedMeshes[ 0 ];
1550
1551		} );
1552
1553	}
1554
1555}
1556
1557/* BINARY EXTENSION */
1558const BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
1559const BINARY_EXTENSION_HEADER_LENGTH = 12;
1560const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 };
1561
1562class GLTFBinaryExtension {
1563
1564	constructor( data ) {
1565
1566		this.name = EXTENSIONS.KHR_BINARY_GLTF;
1567		this.content = null;
1568		this.body = null;
1569
1570		const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );
1571		const textDecoder = new TextDecoder();
1572
1573		this.header = {
1574			magic: textDecoder.decode( new Uint8Array( data.slice( 0, 4 ) ) ),
1575			version: headerView.getUint32( 4, true ),
1576			length: headerView.getUint32( 8, true )
1577		};
1578
1579		if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) {
1580
1581			throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' );
1582
1583		} else if ( this.header.version < 2.0 ) {
1584
1585			throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' );
1586
1587		}
1588
1589		const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH;
1590		const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH );
1591		let chunkIndex = 0;
1592
1593		while ( chunkIndex < chunkContentsLength ) {
1594
vendor: 9,937 bytes, lines 1595-2041
1595			const chunkLength = chunkView.getUint32( chunkIndex, true );
1596			chunkIndex += 4;
1597
1598			const chunkType = chunkView.getUint32( chunkIndex, true );
1599			chunkIndex += 4;
1600
1601			if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) {
1602
1603				const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength );
1604				this.content = textDecoder.decode( contentArray );
1605
1606			} else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) {
1607
1608				const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
1609				this.body = data.slice( byteOffset, byteOffset + chunkLength );
1610
1611			}
1612
1613			// Clients must ignore chunks with unknown types.
1614
1615			chunkIndex += chunkLength;
1616
1617		}
1618
1619		if ( this.content === null ) {
1620
1621			throw new Error( 'THREE.GLTFLoader: JSON content not found.' );
1622
1623		}
1624
1625	}
1626
1627}
1628
1629/**
1630 * DRACO Mesh Compression Extension
1631 *
1632 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression
1633 */
1634class GLTFDracoMeshCompressionExtension {
1635
1636	constructor( json, dracoLoader ) {
1637
1638		if ( ! dracoLoader ) {
1639
1640			throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' );
1641
1642		}
1643
1644		this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
1645		this.json = json;
1646		this.dracoLoader = dracoLoader;
1647		this.dracoLoader.preload();
1648
1649	}
1650
1651	decodePrimitive( primitive, parser ) {
1652
1653		const json = this.json;
1654		const dracoLoader = this.dracoLoader;
1655		const bufferViewIndex = primitive.extensions[ this.name ].bufferView;
1656		const gltfAttributeMap = primitive.extensions[ this.name ].attributes;
1657		const threeAttributeMap = {};
1658		const attributeNormalizedMap = {};
1659		const attributeTypeMap = {};
1660
1661		for ( const attributeName in gltfAttributeMap ) {
1662
1663			const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
1664
1665			threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ];
1666
1667		}
1668
1669		for ( const attributeName in primitive.attributes ) {
1670
1671			const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
1672
1673			if ( gltfAttributeMap[ attributeName ] !== undefined ) {
1674
1675				const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
1676				const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
1677
1678				attributeTypeMap[ threeAttributeName ] = componentType.name;
1679				attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true;
1680
1681			}
1682
1683		}
1684
1685		return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {
1686
1687			return new Promise( function ( resolve ) {
1688
1689				dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {
1690
1691					for ( const attributeName in geometry.attributes ) {
1692
1693						const attribute = geometry.attributes[ attributeName ];
1694						const normalized = attributeNormalizedMap[ attributeName ];
1695
1696						if ( normalized !== undefined ) attribute.normalized = normalized;
1697
1698					}
1699
1700					resolve( geometry );
1701
1702				}, threeAttributeMap, attributeTypeMap );
1703
1704			} );
1705
1706		} );
1707
1708	}
1709
1710}
1711
1712/**
1713 * Texture Transform Extension
1714 *
1715 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform
1716 */
1717class GLTFTextureTransformExtension {
1718
1719	constructor() {
1720
1721		this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;
1722
1723	}
1724
1725	extendTexture( texture, transform ) {
1726
1727		if ( transform.texCoord !== undefined ) {
1728
1729			console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' );
1730
1731		}
1732
1733		if ( transform.offset === undefined && transform.rotation === undefined && transform.scale === undefined ) {
1734
1735			// See https://github.com/mrdoob/three.js/issues/21819.
1736			return texture;
1737
1738		}
1739
1740		texture = texture.clone();
1741
1742		if ( transform.offset !== undefined ) {
1743
1744			texture.offset.fromArray( transform.offset );
1745
1746		}
1747
1748		if ( transform.rotation !== undefined ) {
1749
1750			texture.rotation = transform.rotation;
1751
1752		}
1753
1754		if ( transform.scale !== undefined ) {
1755
1756			texture.repeat.fromArray( transform.scale );
1757
1758		}
1759
1760		texture.needsUpdate = true;
1761
1762		return texture;
1763
1764	}
1765
1766}
1767
1768/**
1769 * Mesh Quantization Extension
1770 *
1771 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization
1772 */
1773class GLTFMeshQuantizationExtension {
1774
1775	constructor() {
1776
1777		this.name = EXTENSIONS.KHR_MESH_QUANTIZATION;
1778
1779	}
1780
1781}
1782
1783/*********************************/
1784/********** INTERPOLATION ********/
1785/*********************************/
1786
1787// Spline Interpolation
1788// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
1789class GLTFCubicSplineInterpolant extends Interpolant {
1790
1791	constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
1792
1793		super( parameterPositions, sampleValues, sampleSize, resultBuffer );
1794
1795	}
1796
1797	copySampleValue_( index ) {
1798
1799		// Copies a sample value to the result buffer. See description of glTF
1800		// CUBICSPLINE values layout in interpolate_() function below.
1801
1802		const result = this.resultBuffer,
1803			values = this.sampleValues,
1804			valueSize = this.valueSize,
1805			offset = index * valueSize * 3 + valueSize;
1806
1807		for ( let i = 0; i !== valueSize; i ++ ) {
1808
1809			result[ i ] = values[ offset + i ];
1810
1811		}
1812
1813		return result;
1814
1815	}
1816
1817	interpolate_( i1, t0, t, t1 ) {
1818
1819		const result = this.resultBuffer;
1820		const values = this.sampleValues;
1821		const stride = this.valueSize;
1822
1823		const stride2 = stride * 2;
1824		const stride3 = stride * 3;
1825
1826		const td = t1 - t0;
1827
1828		const p = ( t - t0 ) / td;
1829		const pp = p * p;
1830		const ppp = pp * p;
1831
1832		const offset1 = i1 * stride3;
1833		const offset0 = offset1 - stride3;
1834
1835		const s2 = - 2 * ppp + 3 * pp;
1836		const s3 = ppp - pp;
1837		const s0 = 1 - s2;
1838		const s1 = s3 - pp + p;
1839
1840		// Layout of keyframe output values for CUBICSPLINE animations:
1841		//   [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
1842		for ( let i = 0; i !== stride; i ++ ) {
1843
1844			const p0 = values[ offset0 + i + stride ]; // splineVertex_k
1845			const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
1846			const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
1847			const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
1848
1849			result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
1850
1851		}
1852
1853		return result;
1854
1855	}
1856
1857}
1858
1859const _q = new Quaternion();
1860
1861class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant {
1862
1863	interpolate_( i1, t0, t, t1 ) {
1864
1865		const result = super.interpolate_( i1, t0, t, t1 );
1866
1867		_q.fromArray( result ).normalize().toArray( result );
1868
1869		return result;
1870
1871	}
1872
1873}
1874
1875
1876/*********************************/
1877/********** INTERNALS ************/
1878/*********************************/
1879
1880/* CONSTANTS */
1881
1882const WEBGL_CONSTANTS = {
1883	FLOAT: 5126,
1884	//FLOAT_MAT2: 35674,
1885	FLOAT_MAT3: 35675,
1886	FLOAT_MAT4: 35676,
1887	FLOAT_VEC2: 35664,
1888	FLOAT_VEC3: 35665,
1889	FLOAT_VEC4: 35666,
1890	LINEAR: 9729,
1891	REPEAT: 10497,
1892	SAMPLER_2D: 35678,
1893	POINTS: 0,
1894	LINES: 1,
1895	LINE_LOOP: 2,
1896	LINE_STRIP: 3,
1897	TRIANGLES: 4,
1898	TRIANGLE_STRIP: 5,
1899	TRIANGLE_FAN: 6,
1900	UNSIGNED_BYTE: 5121,
1901	UNSIGNED_SHORT: 5123
1902};
1903
1904const WEBGL_COMPONENT_TYPES = {
1905	5120: Int8Array,
1906	5121: Uint8Array,
1907	5122: Int16Array,
1908	5123: Uint16Array,
1909	5125: Uint32Array,
1910	5126: Float32Array
1911};
1912
1913const WEBGL_FILTERS = {
1914	9728: NearestFilter,
1915	9729: LinearFilter,
1916	9984: NearestMipmapNearestFilter,
1917	9985: LinearMipmapNearestFilter,
1918	9986: NearestMipmapLinearFilter,
1919	9987: LinearMipmapLinearFilter
1920};
1921
1922const WEBGL_WRAPPINGS = {
1923	33071: ClampToEdgeWrapping,
1924	33648: MirroredRepeatWrapping,
1925	10497: RepeatWrapping
1926};
1927
1928const WEBGL_TYPE_SIZES = {
1929	'SCALAR': 1,
1930	'VEC2': 2,
1931	'VEC3': 3,
1932	'VEC4': 4,
1933	'MAT2': 4,
1934	'MAT3': 9,
1935	'MAT4': 16
1936};
1937
1938const ATTRIBUTES = {
1939	POSITION: 'position',
1940	NORMAL: 'normal',
1941	TANGENT: 'tangent',
1942	TEXCOORD_0: 'uv',
1943	TEXCOORD_1: 'uv2',
1944	COLOR_0: 'color',
1945	WEIGHTS_0: 'skinWeight',
1946	JOINTS_0: 'skinIndex',
1947};
1948
1949const PATH_PROPERTIES = {
1950	scale: 'scale',
1951	translation: 'position',
1952	rotation: 'quaternion',
1953	weights: 'morphTargetInfluences'
1954};
1955
1956const INTERPOLATION = {
1957	CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
1958		                        // keyframe track will be initialized with a default interpolation type, then modified.
1959	LINEAR: InterpolateLinear,
1960	STEP: InterpolateDiscrete
1961};
1962
1963const ALPHA_MODES = {
1964	OPAQUE: 'OPAQUE',
1965	MASK: 'MASK',
1966	BLEND: 'BLEND'
1967};
1968
1969/**
1970 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
1971 */
1972function createDefaultMaterial( cache ) {
1973
1974	if ( cache[ 'DefaultMaterial' ] === undefined ) {
1975
1976		cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( {
1977			color: 0xFFFFFF,
1978			emissive: 0x000000,
1979			metalness: 1,
1980			roughness: 1,
1981			transparent: false,
1982			depthTest: true,
1983			side: FrontSide
1984		} );
1985
1986	}
1987
1988	return cache[ 'DefaultMaterial' ];
1989
1990}
1991
1992function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {
1993
1994	// Add unknown glTF extensions to an object's userData.
1995
1996	for ( const name in objectDef.extensions ) {
1997
1998		if ( knownExtensions[ name ] === undefined ) {
1999
2000			object.userData.gltfExtensions = object.userData.gltfExtensions || {};
2001			object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ];
2002
2003		}
2004
2005	}
2006
2007}
2008
2009/**
2010 * @param {Object3D|Material|BufferGeometry} object
2011 * @param {GLTF.definition} gltfDef
2012 */
2013function assignExtrasToUserData( object, gltfDef ) {
2014
2015	if ( gltfDef.extras !== undefined ) {
2016
2017		if ( typeof gltfDef.extras === 'object' ) {
2018
2019			Object.assign( object.userData, gltfDef.extras );
2020
2021		} else {
2022
2023			console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras );
2024
2025		}
2026
2027	}
2028
2029}
2030
2031/**
2032 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
2033 *
2034 * @param {BufferGeometry} geometry
2035 * @param {Array<GLTF.Target>} targets
2036 * @param {GLTFParser} parser
2037 * @return {Promise<BufferGeometry>}
2038 */
2039function addMorphTargets( geometry, targets, parser ) {
2040
2041	let hasMorphPosition = false;
vendor: 4,804 bytes, lines 2042-2253
2042	let hasMorphNormal = false;
2043	let hasMorphColor = false;
2044
2045	for ( let i = 0, il = targets.length; i < il; i ++ ) {
2046
2047		const target = targets[ i ];
2048
2049		if ( target.POSITION !== undefined ) hasMorphPosition = true;
2050		if ( target.NORMAL !== undefined ) hasMorphNormal = true;
2051		if ( target.COLOR_0 !== undefined ) hasMorphColor = true;
2052
2053		if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break;
2054
2055	}
2056
2057	if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry );
2058
2059	const pendingPositionAccessors = [];
2060	const pendingNormalAccessors = [];
2061	const pendingColorAccessors = [];
2062
2063	for ( let i = 0, il = targets.length; i < il; i ++ ) {
2064
2065		const target = targets[ i ];
2066
2067		if ( hasMorphPosition ) {
2068
2069			const pendingAccessor = target.POSITION !== undefined
2070				? parser.getDependency( 'accessor', target.POSITION )
2071				: geometry.attributes.position;
2072
2073			pendingPositionAccessors.push( pendingAccessor );
2074
2075		}
2076
2077		if ( hasMorphNormal ) {
2078
2079			const pendingAccessor = target.NORMAL !== undefined
2080				? parser.getDependency( 'accessor', target.NORMAL )
2081				: geometry.attributes.normal;
2082
2083			pendingNormalAccessors.push( pendingAccessor );
2084
2085		}
2086
2087		if ( hasMorphColor ) {
2088
2089			const pendingAccessor = target.COLOR_0 !== undefined
2090				? parser.getDependency( 'accessor', target.COLOR_0 )
2091				: geometry.attributes.color;
2092
2093			pendingColorAccessors.push( pendingAccessor );
2094
2095		}
2096
2097	}
2098
2099	return Promise.all( [
2100		Promise.all( pendingPositionAccessors ),
2101		Promise.all( pendingNormalAccessors ),
2102		Promise.all( pendingColorAccessors )
2103	] ).then( function ( accessors ) {
2104
2105		const morphPositions = accessors[ 0 ];
2106		const morphNormals = accessors[ 1 ];
2107		const morphColors = accessors[ 2 ];
2108
2109		if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
2110		if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
2111		if ( hasMorphColor ) geometry.morphAttributes.color = morphColors;
2112		geometry.morphTargetsRelative = true;
2113
2114		return geometry;
2115
2116	} );
2117
2118}
2119
2120/**
2121 * @param {Mesh} mesh
2122 * @param {GLTF.Mesh} meshDef
2123 */
2124function updateMorphTargets( mesh, meshDef ) {
2125
2126	mesh.updateMorphTargets();
2127
2128	if ( meshDef.weights !== undefined ) {
2129
2130		for ( let i = 0, il = meshDef.weights.length; i < il; i ++ ) {
2131
2132			mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ];
2133
2134		}
2135
2136	}
2137
2138	// .extras has user-defined data, so check that .extras.targetNames is an array.
2139	if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) {
2140
2141		const targetNames = meshDef.extras.targetNames;
2142
2143		if ( mesh.morphTargetInfluences.length === targetNames.length ) {
2144
2145			mesh.morphTargetDictionary = {};
2146
2147			for ( let i = 0, il = targetNames.length; i < il; i ++ ) {
2148
2149				mesh.morphTargetDictionary[ targetNames[ i ] ] = i;
2150
2151			}
2152
2153		} else {
2154
2155			console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' );
2156
2157		}
2158
2159	}
2160
2161}
2162
2163function createPrimitiveKey( primitiveDef ) {
2164
2165	const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
2166	let geometryKey;
2167
2168	if ( dracoExtension ) {
2169
2170		geometryKey = 'draco:' + dracoExtension.bufferView
2171				+ ':' + dracoExtension.indices
2172				+ ':' + createAttributesKey( dracoExtension.attributes );
2173
2174	} else {
2175
2176		geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode;
2177
2178	}
2179
2180	return geometryKey;
2181
2182}
2183
2184function createAttributesKey( attributes ) {
2185
2186	let attributesKey = '';
2187
2188	const keys = Object.keys( attributes ).sort();
2189
2190	for ( let i = 0, il = keys.length; i < il; i ++ ) {
2191
2192		attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';';
2193
2194	}
2195
2196	return attributesKey;
2197
2198}
2199
2200function getNormalizedComponentScale( constructor ) {
2201
2202	// Reference:
2203	// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data
2204
2205	switch ( constructor ) {
2206
2207		case Int8Array:
2208			return 1 / 127;
2209
2210		case Uint8Array:
2211			return 1 / 255;
2212
2213		case Int16Array:
2214			return 1 / 32767;
2215
2216		case Uint16Array:
2217			return 1 / 65535;
2218
2219		default:
2220			throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' );
2221
2222	}
2223
2224}
2225
2226function getImageURIMimeType( uri ) {
2227
2228	if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg';
2229	if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp';
2230
2231	return 'image/png';
2232
2233}
2234
2235const _identityMatrix = new Matrix4();
2236
2237/* GLTF PARSER */
2238
2239class GLTFParser {
2240
2241	constructor( json = {}, options = {} ) {
2242
2243		this.json = json;
2244		this.extensions = {};
2245		this.plugins = {};
2246		this.options = options;
2247
2248		// loader object cache
2249		this.cache = new GLTFRegistry();
2250
2251		// associations between Three.js objects and glTF elements
2252		this.associations = new Map();
2253
2254		// BufferGeometry caching
2255		this.primitiveCache = {};
2256
2257		// Object3D instance caches
2258		this.meshCache = { refs: {}, uses: {} };
2259		this.cameraCache = { refs: {}, uses: {} };
2260		this.lightCache = { refs: {}, uses: {} };
2261
2262		this.sourceCache = {};
2263		this.textureCache = {};
2264
2265		// Track node names, to ensure no duplicates
2266		this.nodeNamesUsed = {};
2267
2268		// Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the
2269		// expensive work of uploading a texture to the GPU off the main thread.
2270
2271		let isSafari = false;
2272		let isFirefox = false;
2273		let firefoxVersion = - 1;
2274
2275		if ( typeof navigator !== 'undefined' ) {
2276
2277			isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === true;
2278			isFirefox = navigator.userAgent.indexOf( 'Firefox' ) > - 1;
2279			firefoxVersion = isFirefox ? navigator.userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1;
2280
2281		}
2282
2283		if ( typeof createImageBitmap === 'undefined' || isSafari || ( isFirefox && firefoxVersion < 98 ) ) {
2284
2285			this.textureLoader = new TextureLoader( this.options.manager );
2286
2287		} else {
2288
2289			this.textureLoader = new ImageBitmapLoader( this.options.manager );
2290
2291		}
2292
2293		this.textureLoader.setCrossOrigin( this.options.crossOrigin );
2294		this.textureLoader.setRequestHeader( this.options.requestHeader );
2295
2296		this.fileLoader = new FileLoader( this.options.manager );
2297		this.fileLoader.setResponseType( 'arraybuffer' );
2298
2299		if ( this.options.crossOrigin === 'use-credentials' ) {
2300
2301			this.fileLoader.setWithCredentials( true );
2302
2303		}
2304
2305	}
2306
2307	setExtensions( extensions ) {
2308
2309		this.extensions = extensions;
2310
2311	}
2312
2313	setPlugins( plugins ) {
2314
2315		this.plugins = plugins;
2316
2317	}
2318
2319	parse( onLoad, onError ) {
2320
2321		const parser = this;
2322		const json = this.json;
2323		const extensions = this.extensions;
2324
2325		// Clear the loader cache
2326		this.cache.removeAll();
2327
2328		// Mark the special nodes/meshes in json for efficient parse
2329		this._invokeAll( function ( ext ) {
2330
2331			return ext._markDefs && ext._markDefs();
2332
2333		} );
2334
2335		Promise.all( this._invokeAll( function ( ext ) {
2336
2337			return ext.beforeRoot && ext.beforeRoot();
2338
2339		} ) ).then( function () {
2340
2341			return Promise.all( [
2342
2343				parser.getDependencies( 'scene' ),
2344				parser.getDependencies( 'animation' ),
2345				parser.getDependencies( 'camera' ),
2346
2347			] );
2348
2349		} ).then( function ( dependencies ) {
2350
2351			const result = {
2352				scene: dependencies[ 0 ][ json.scene || 0 ],
2353				scenes: dependencies[ 0 ],
2354				animations: dependencies[ 1 ],
2355				cameras: dependencies[ 2 ],
2356				asset: json.asset,
2357				parser: parser,
2358				userData: {}
2359			};
2360
2361			addUnknownExtensionsToUserData( extensions, result, json );
2362
2363			assignExtrasToUserData( result, json );
2364
2365			Promise.all( parser._invokeAll( function ( ext ) {
2366
2367				return ext.afterRoot && ext.afterRoot( result );
2368
2369			} ) ).then( function () {
2370
2371				onLoad( result );
2372
2373			} );
2374
2375		} ).catch( onError );
2376
2377	}
2378
2379	/**
2380	 * Marks the special nodes/meshes in json for efficient parse.
2381	 */
2382	_markDefs() {
2383
2384		const nodeDefs = this.json.nodes || [];
2385		const skinDefs = this.json.skins || [];
2386		const meshDefs = this.json.meshes || [];
2387
2388		// Nothing in the node definition indicates whether it is a Bone or an
2389		// Object3D. Use the skins' joint references to mark bones.
2390		for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) {
2391
2392			const joints = skinDefs[ skinIndex ].joints;
2393
2394			for ( let i = 0, il = joints.length; i < il; i ++ ) {
2395
2396				nodeDefs[ joints[ i ] ].isBone = true;
2397
2398			}
2399
2400		}
2401
2402		// Iterate over all nodes, marking references to shared resources,
2403		// as well as skeleton joints.
2404		for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
2405
2406			const nodeDef = nodeDefs[ nodeIndex ];
2407
2408			if ( nodeDef.mesh !== undefined ) {
2409
2410				this._addNodeRef( this.meshCache, nodeDef.mesh );
2411
2412				// Nothing in the mesh definition indicates whether it is
2413				// a SkinnedMesh or Mesh. Use the node's mesh reference
2414				// to mark SkinnedMesh if node has skin.
2415				if ( nodeDef.skin !== undefined ) {
2416
2417					meshDefs[ nodeDef.mesh ].isSkinnedMesh = true;
2418
2419				}
2420
2421			}
2422
2423			if ( nodeDef.camera !== undefined ) {
2424
2425				this._addNodeRef( this.cameraCache, nodeDef.camera );
2426
2427			}
2428
2429		}
2430
2431	}
2432
vendor: 9,216 bytes, lines 2433-2804
2433	/**
2434	 * Counts references to shared node / Object3D resources. These resources
2435	 * can be reused, or "instantiated", at multiple nodes in the scene
2436	 * hierarchy. Mesh, Camera, and Light instances are instantiated and must
2437	 * be marked. Non-scenegraph resources (like Materials, Geometries, and
2438	 * Textures) can be reused directly and are not marked here.
2439	 *
2440	 * Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
2441	 */
2442	_addNodeRef( cache, index ) {
2443
2444		if ( index === undefined ) return;
2445
2446		if ( cache.refs[ index ] === undefined ) {
2447
2448			cache.refs[ index ] = cache.uses[ index ] = 0;
2449
2450		}
2451
2452		cache.refs[ index ] ++;
2453
2454	}
2455
2456	/** Returns a reference to a shared resource, cloning it if necessary. */
2457	_getNodeRef( cache, index, object ) {
2458
2459		if ( cache.refs[ index ] <= 1 ) return object;
2460
2461		const ref = object.clone();
2462
2463		// Propagates mappings to the cloned object, prevents mappings on the
2464		// original object from being lost.
2465		const updateMappings = ( original, clone ) => {
2466
2467			const mappings = this.associations.get( original );
2468			if ( mappings != null ) {
2469
2470				this.associations.set( clone, mappings );
2471
2472			}
2473
2474			for ( const [ i, child ] of original.children.entries() ) {
2475
2476				updateMappings( child, clone.children[ i ] );
2477
2478			}
2479
2480		};
2481
2482		updateMappings( object, ref );
2483
2484		ref.name += '_instance_' + ( cache.uses[ index ] ++ );
2485
2486		return ref;
2487
2488	}
2489
2490	_invokeOne( func ) {
2491
2492		const extensions = Object.values( this.plugins );
2493		extensions.push( this );
2494
2495		for ( let i = 0; i < extensions.length; i ++ ) {
2496
2497			const result = func( extensions[ i ] );
2498
2499			if ( result ) return result;
2500
2501		}
2502
2503		return null;
2504
2505	}
2506
2507	_invokeAll( func ) {
2508
2509		const extensions = Object.values( this.plugins );
2510		extensions.unshift( this );
2511
2512		const pending = [];
2513
2514		for ( let i = 0; i < extensions.length; i ++ ) {
2515
2516			const result = func( extensions[ i ] );
2517
2518			if ( result ) pending.push( result );
2519
2520		}
2521
2522		return pending;
2523
2524	}
2525
2526	/**
2527	 * Requests the specified dependency asynchronously, with caching.
2528	 * @param {string} type
2529	 * @param {number} index
2530	 * @return {Promise<Object3D|Material|THREE.Texture|AnimationClip|ArrayBuffer|Object>}
2531	 */
2532	getDependency( type, index ) {
2533
2534		const cacheKey = type + ':' + index;
2535		let dependency = this.cache.get( cacheKey );
2536
2537		if ( ! dependency ) {
2538
2539			switch ( type ) {
2540
2541				case 'scene':
2542					dependency = this.loadScene( index );
2543					break;
2544
2545				case 'node':
2546					dependency = this._invokeOne( function ( ext ) {
2547
2548						return ext.loadNode && ext.loadNode( index );
2549
2550					} );
2551					break;
2552
2553				case 'mesh':
2554					dependency = this._invokeOne( function ( ext ) {
2555
2556						return ext.loadMesh && ext.loadMesh( index );
2557
2558					} );
2559					break;
2560
2561				case 'accessor':
2562					dependency = this.loadAccessor( index );
2563					break;
2564
2565				case 'bufferView':
2566					dependency = this._invokeOne( function ( ext ) {
2567
2568						return ext.loadBufferView && ext.loadBufferView( index );
2569
2570					} );
2571					break;
2572
2573				case 'buffer':
2574					dependency = this.loadBuffer( index );
2575					break;
2576
2577				case 'material':
2578					dependency = this._invokeOne( function ( ext ) {
2579
2580						return ext.loadMaterial && ext.loadMaterial( index );
2581
2582					} );
2583					break;
2584
2585				case 'texture':
2586					dependency = this._invokeOne( function ( ext ) {
2587
2588						return ext.loadTexture && ext.loadTexture( index );
2589
2590					} );
2591					break;
2592
2593				case 'skin':
2594					dependency = this.loadSkin( index );
2595					break;
2596
2597				case 'animation':
2598					dependency = this._invokeOne( function ( ext ) {
2599
2600						return ext.loadAnimation && ext.loadAnimation( index );
2601
2602					} );
2603					break;
2604
2605				case 'camera':
2606					dependency = this.loadCamera( index );
2607					break;
2608
2609				default:
2610					dependency = this._invokeOne( function ( ext ) {
2611
2612						return ext != this && ext.getDependency && ext.getDependency( type, index );
2613
2614					} );
2615
2616					if ( ! dependency ) {
2617
2618						throw new Error( 'Unknown type: ' + type );
2619
2620					}
2621
2622					break;
2623
2624			}
2625
2626			this.cache.add( cacheKey, dependency );
2627
2628		}
2629
2630		return dependency;
2631
2632	}
2633
2634	/**
2635	 * Requests all dependencies of the specified type asynchronously, with caching.
2636	 * @param {string} type
2637	 * @return {Promise<Array<Object>>}
2638	 */
2639	getDependencies( type ) {
2640
2641		let dependencies = this.cache.get( type );
2642
2643		if ( ! dependencies ) {
2644
2645			const parser = this;
2646			const defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || [];
2647
2648			dependencies = Promise.all( defs.map( function ( def, index ) {
2649
2650				return parser.getDependency( type, index );
2651
2652			} ) );
2653
2654			this.cache.add( type, dependencies );
2655
2656		}
2657
2658		return dependencies;
2659
2660	}
2661
2662	/**
2663	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
2664	 * @param {number} bufferIndex
2665	 * @return {Promise<ArrayBuffer>}
2666	 */
2667	loadBuffer( bufferIndex ) {
2668
2669		const bufferDef = this.json.buffers[ bufferIndex ];
2670		const loader = this.fileLoader;
2671
2672		if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) {
2673
2674			throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' );
2675
2676		}
2677
2678		// If present, GLB container is required to be the first buffer.
2679		if ( bufferDef.uri === undefined && bufferIndex === 0 ) {
2680
2681			return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body );
2682
2683		}
2684
2685		const options = this.options;
2686
2687		return new Promise( function ( resolve, reject ) {
2688
2689			loader.load( LoaderUtils.resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () {
2690
2691				reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) );
2692
2693			} );
2694
2695		} );
2696
2697	}
2698
2699	/**
2700	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
2701	 * @param {number} bufferViewIndex
2702	 * @return {Promise<ArrayBuffer>}
2703	 */
2704	loadBufferView( bufferViewIndex ) {
2705
2706		const bufferViewDef = this.json.bufferViews[ bufferViewIndex ];
2707
2708		return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) {
2709
2710			const byteLength = bufferViewDef.byteLength || 0;
2711			const byteOffset = bufferViewDef.byteOffset || 0;
2712			return buffer.slice( byteOffset, byteOffset + byteLength );
2713
2714		} );
2715
2716	}
2717
2718	/**
2719	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
2720	 * @param {number} accessorIndex
2721	 * @return {Promise<BufferAttribute|InterleavedBufferAttribute>}
2722	 */
2723	loadAccessor( accessorIndex ) {
2724
2725		const parser = this;
2726		const json = this.json;
2727
2728		const accessorDef = this.json.accessors[ accessorIndex ];
2729
2730		if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) {
2731
2732			const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ];
2733			const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
2734			const normalized = accessorDef.normalized === true;
2735
2736			const array = new TypedArray( accessorDef.count * itemSize );
2737			return Promise.resolve( new BufferAttribute( array, itemSize, normalized ) );
2738
2739		}
2740
2741		const pendingBufferViews = [];
2742
2743		if ( accessorDef.bufferView !== undefined ) {
2744
2745			pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) );
2746
2747		} else {
2748
2749			pendingBufferViews.push( null );
2750
2751		}
2752
2753		if ( accessorDef.sparse !== undefined ) {
2754
2755			pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) );
2756			pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) );
2757
2758		}
2759
2760		return Promise.all( pendingBufferViews ).then( function ( bufferViews ) {
2761
2762			const bufferView = bufferViews[ 0 ];
2763
2764			const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ];
2765			const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
2766
2767			// For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
2768			const elementBytes = TypedArray.BYTES_PER_ELEMENT;
2769			const itemBytes = elementBytes * itemSize;
2770			const byteOffset = accessorDef.byteOffset || 0;
2771			const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined;
2772			const normalized = accessorDef.normalized === true;
2773			let array, bufferAttribute;
2774
2775			// The buffer is not interleaved if the stride is the item size in bytes.
2776			if ( byteStride && byteStride !== itemBytes ) {
2777
2778				// Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer
2779				// This makes sure that IBA.count reflects accessor.count properly
2780				const ibSlice = Math.floor( byteOffset / byteStride );
2781				const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count;
2782				let ib = parser.cache.get( ibCacheKey );
2783
2784				if ( ! ib ) {
2785
2786					array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes );
2787
2788					// Integer parameters to IB/IBA are in array elements, not bytes.
2789					ib = new InterleavedBuffer( array, byteStride / elementBytes );
2790
2791					parser.cache.add( ibCacheKey, ib );
2792
2793				}
2794
2795				bufferAttribute = new InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized );
2796
2797			} else {
2798
2799				if ( bufferView === null ) {
2800
2801					array = new TypedArray( accessorDef.count * itemSize );
2802
2803				} else {
2804
vendor: 5,007 bytes, lines 2805-2981
2805					array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize );
2806
2807				}
2808
2809				bufferAttribute = new BufferAttribute( array, itemSize, normalized );
2810
2811			}
2812
2813			// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
2814			if ( accessorDef.sparse !== undefined ) {
2815
2816				const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
2817				const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ];
2818
2819				const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
2820				const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;
2821
2822				const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices );
2823				const sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize );
2824
2825				if ( bufferView !== null ) {
2826
2827					// Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
2828					bufferAttribute = new BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized );
2829
2830				}
2831
2832				for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) {
2833
2834					const index = sparseIndices[ i ];
2835
2836					bufferAttribute.setX( index, sparseValues[ i * itemSize ] );
2837					if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] );
2838					if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] );
2839					if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] );
2840					if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' );
2841
2842				}
2843
2844			}
2845
2846			return bufferAttribute;
2847
2848		} );
2849
2850	}
2851
2852	/**
2853	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
2854	 * @param {number} textureIndex
2855	 * @return {Promise<THREE.Texture|null>}
2856	 */
2857	loadTexture( textureIndex ) {
2858
2859		const json = this.json;
2860		const options = this.options;
2861		const textureDef = json.textures[ textureIndex ];
2862		const sourceIndex = textureDef.source;
2863		const sourceDef = json.images[ sourceIndex ];
2864
2865		let loader = this.textureLoader;
2866
2867		if ( sourceDef.uri ) {
2868
2869			const handler = options.manager.getHandler( sourceDef.uri );
2870			if ( handler !== null ) loader = handler;
2871
2872		}
2873
2874		return this.loadTextureImage( textureIndex, sourceIndex, loader );
2875
2876	}
2877
2878	loadTextureImage( textureIndex, sourceIndex, loader ) {
2879
2880		const parser = this;
2881		const json = this.json;
2882
2883		const textureDef = json.textures[ textureIndex ];
2884		const sourceDef = json.images[ sourceIndex ];
2885
2886		const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler;
2887
2888		if ( this.textureCache[ cacheKey ] ) {
2889
2890			// See https://github.com/mrdoob/three.js/issues/21559.
2891			return this.textureCache[ cacheKey ];
2892
2893		}
2894
2895		const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) {
2896
2897			texture.flipY = false;
2898
2899			texture.name = textureDef.name || sourceDef.name || '';
2900
2901			const samplers = json.samplers || {};
2902			const sampler = samplers[ textureDef.sampler ] || {};
2903
2904			texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter;
2905			texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter;
2906			texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping;
2907			texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping;
2908
2909			parser.associations.set( texture, { textures: textureIndex } );
2910
2911			return texture;
2912
2913		} ).catch( function () {
2914
2915			return null;
2916
2917		} );
2918
2919		this.textureCache[ cacheKey ] = promise;
2920
2921		return promise;
2922
2923	}
2924
2925	loadImageSource( sourceIndex, loader ) {
2926
2927		const parser = this;
2928		const json = this.json;
2929		const options = this.options;
2930
2931		if ( this.sourceCache[ sourceIndex ] !== undefined ) {
2932
2933			return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() );
2934
2935		}
2936
2937		const sourceDef = json.images[ sourceIndex ];
2938
2939		const URL = self.URL || self.webkitURL;
2940
2941		let sourceURI = sourceDef.uri || '';
2942		let isObjectURL = false;
2943
2944		if ( sourceDef.bufferView !== undefined ) {
2945
2946			// Load binary image data from bufferView, if provided.
2947
2948			sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) {
2949
2950				isObjectURL = true;
2951				const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } );
2952				sourceURI = URL.createObjectURL( blob );
2953				return sourceURI;
2954
2955			} );
2956
2957		} else if ( sourceDef.uri === undefined ) {
2958
2959			throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' );
2960
2961		}
2962
2963		const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) {
2964
2965			return new Promise( function ( resolve, reject ) {
2966
2967				let onLoad = resolve;
2968
2969				if ( loader.isImageBitmapLoader === true ) {
2970
2971					onLoad = function ( imageBitmap ) {
2972
2973						const texture = new Texture( imageBitmap );
2974						texture.needsUpdate = true;
2975
2976						resolve( texture );
2977
2978					};
2979
2980				}
2981
2982				loader.load( LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject );
2983
2984			} );
2985
2986		} ).then( function ( texture ) {
2987
2988			// Clean up resources and configure Texture.
2989
2990			if ( isObjectURL === true ) {
2991
2992				URL.revokeObjectURL( sourceURI );
2993
2994			}
2995
2996			texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri );
2997
2998			return texture;
2999
3000		} ).catch( function ( error ) {
3001
3002			console.error( 'THREE.GLTFLoader: Couldn\'t load texture', sourceURI );
3003			throw error;
3004
3005		} );
3006
3007		this.sourceCache[ sourceIndex ] = promise;
3008		return promise;
3009
3010	}
3011
3012	/**
3013	 * Asynchronously assigns a texture to the given material parameters.
3014	 * @param {Object} materialParams
3015	 * @param {string} mapName
3016	 * @param {Object} mapDef
3017	 * @return {Promise<Texture>}
3018	 */
3019	assignTexture( materialParams, mapName, mapDef, encoding ) {
3020
3021		const parser = this;
3022
3023		return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) {
3024
3025			if ( ! texture ) return null;
3026
3027			// Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured
3028			// However, we will copy UV set 0 to UV set 1 on demand for aoMap
3029			if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) {
3030
3031				console.warn( 'THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' );
3032
3033			}
3034
3035			if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) {
3036
3037				const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined;
3038
3039				if ( transform ) {
3040
3041					const gltfReference = parser.associations.get( texture );
3042					texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
3043					parser.associations.set( texture, gltfReference );
3044
3045				}
3046
3047			}
3048
3049			if ( encoding !== undefined ) {
3050
3051				texture.encoding = encoding;
3052
3053			}
3054
3055			materialParams[ mapName ] = texture;
3056
3057			return texture;
3058
3059		} );
3060
3061	}
3062
3063	/**
3064	 * Assigns final material to a Mesh, Line, or Points instance. The instance
3065	 * already has a material (generated from the glTF material options alone)
3066	 * but reuse of the same glTF material may require multiple threejs materials
3067	 * to accommodate different primitive types, defines, etc. New materials will
3068	 * be created if necessary, and reused from a cache.
3069	 * @param  {Object3D} mesh Mesh, Line, or Points instance.
3070	 */
3071	assignFinalMaterial( mesh ) {
3072
3073		const geometry = mesh.geometry;
3074		let material = mesh.material;
3075
3076		const useDerivativeTangents = geometry.attributes.tangent === undefined;
3077		const useVertexColors = geometry.attributes.color !== undefined;
3078		const useFlatShading = geometry.attributes.normal === undefined;
3079
3080		if ( mesh.isPoints ) {
3081
3082			const cacheKey = 'PointsMaterial:' + material.uuid;
3083
3084			let pointsMaterial = this.cache.get( cacheKey );
3085
3086			if ( ! pointsMaterial ) {
3087
3088				pointsMaterial = new PointsMaterial();
3089				Material.prototype.copy.call( pointsMaterial, material );
3090				pointsMaterial.color.copy( material.color );
3091				pointsMaterial.map = material.map;
3092				pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
3093
3094				this.cache.add( cacheKey, pointsMaterial );
3095
3096			}
3097
3098			material = pointsMaterial;
3099
3100		} else if ( mesh.isLine ) {
3101
3102			const cacheKey = 'LineBasicMaterial:' + material.uuid;
3103
3104			let lineMaterial = this.cache.get( cacheKey );
3105
3106			if ( ! lineMaterial ) {
3107
3108				lineMaterial = new LineBasicMaterial();
3109				Material.prototype.copy.call( lineMaterial, material );
3110				lineMaterial.color.copy( material.color );
3111
3112				this.cache.add( cacheKey, lineMaterial );
3113
3114			}
3115
3116			material = lineMaterial;
3117
3118		}
3119
3120		// Clone the material if it will be modified
3121		if ( useDerivativeTangents || useVertexColors || useFlatShading ) {
3122
3123			let cacheKey = 'ClonedMaterial:' + material.uuid + ':';
3124
3125			if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:';
3126			if ( useVertexColors ) cacheKey += 'vertex-colors:';
3127			if ( useFlatShading ) cacheKey += 'flat-shading:';
3128
3129			let cachedMaterial = this.cache.get( cacheKey );
3130
3131			if ( ! cachedMaterial ) {
3132
3133				cachedMaterial = material.clone();
3134
3135				if ( useVertexColors ) cachedMaterial.vertexColors = true;
3136				if ( useFlatShading ) cachedMaterial.flatShading = true;
3137
3138				if ( useDerivativeTangents ) {
3139
3140					// https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
3141					if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1;
3142					if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1;
3143
3144				}
3145
3146				this.cache.add( cacheKey, cachedMaterial );
3147
3148				this.associations.set( cachedMaterial, this.associations.get( material ) );
3149
3150			}
3151
3152			material = cachedMaterial;
3153
3154		}
3155
3156		// workarounds for mesh and geometry
3157
3158		if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
3159
3160			geometry.setAttribute( 'uv2', geometry.attributes.uv );
3161
3162		}
3163
3164		mesh.material = material;
3165
3166	}
3167
3168	getMaterialType( /* materialIndex */ ) {
3169
3170		return MeshStandardMaterial;
3171
3172	}
3173
vendor: 4,465 bytes, lines 3174-3331
3174	/**
3175	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
3176	 * @param {number} materialIndex
3177	 * @return {Promise<Material>}
3178	 */
3179	loadMaterial( materialIndex ) {
3180
3181		const parser = this;
3182		const json = this.json;
3183		const extensions = this.extensions;
3184		const materialDef = json.materials[ materialIndex ];
3185
3186		let materialType;
3187		const materialParams = {};
3188		const materialExtensions = materialDef.extensions || {};
3189
3190		const pending = [];
3191
3192		if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) {
3193
3194			const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ];
3195			materialType = kmuExtension.getMaterialType();
3196			pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) );
3197
3198		} else {
3199
3200			// Specification:
3201			// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
3202
3203			const metallicRoughness = materialDef.pbrMetallicRoughness || {};
3204
3205			materialParams.color = new Color( 1.0, 1.0, 1.0 );
3206			materialParams.opacity = 1.0;
3207
3208			if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
3209
3210				const array = metallicRoughness.baseColorFactor;
3211
3212				materialParams.color.fromArray( array );
3213				materialParams.opacity = array[ 3 ];
3214
3215			}
3216
3217			if ( metallicRoughness.baseColorTexture !== undefined ) {
3218
3219				pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, sRGBEncoding ) );
3220
3221			}
3222
3223			materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
3224			materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;
3225
3226			if ( metallicRoughness.metallicRoughnessTexture !== undefined ) {
3227
3228				pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) );
3229				pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) );
3230
3231			}
3232
3233			materialType = this._invokeOne( function ( ext ) {
3234
3235				return ext.getMaterialType && ext.getMaterialType( materialIndex );
3236
3237			} );
3238
3239			pending.push( Promise.all( this._invokeAll( function ( ext ) {
3240
3241				return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams );
3242
3243			} ) ) );
3244
3245		}
3246
3247		if ( materialDef.doubleSided === true ) {
3248
3249			materialParams.side = DoubleSide;
3250
3251		}
3252
3253		const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE;
3254
3255		if ( alphaMode === ALPHA_MODES.BLEND ) {
3256
3257			materialParams.transparent = true;
3258
3259			// See: https://github.com/mrdoob/three.js/issues/17706
3260			materialParams.depthWrite = false;
3261
3262		} else {
3263
3264			materialParams.transparent = false;
3265
3266			if ( alphaMode === ALPHA_MODES.MASK ) {
3267
3268				materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
3269
3270			}
3271
3272		}
3273
3274		if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) {
3275
3276			pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) );
3277
3278			materialParams.normalScale = new Vector2( 1, 1 );
3279
3280			if ( materialDef.normalTexture.scale !== undefined ) {
3281
3282				const scale = materialDef.normalTexture.scale;
3283
3284				materialParams.normalScale.set( scale, scale );
3285
3286			}
3287
3288		}
3289
3290		if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) {
3291
3292			pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) );
3293
3294			if ( materialDef.occlusionTexture.strength !== undefined ) {
3295
3296				materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;
3297
3298			}
3299
3300		}
3301
3302		if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) {
3303
3304			materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor );
3305
3306		}
3307
3308		if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) {
3309
3310			pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, sRGBEncoding ) );
3311
3312		}
3313
3314		return Promise.all( pending ).then( function () {
3315
3316			const material = new materialType( materialParams );
3317
3318			if ( materialDef.name ) material.name = materialDef.name;
3319
3320			assignExtrasToUserData( material, materialDef );
3321
3322			parser.associations.set( material, { materials: materialIndex } );
3323
3324			if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
3325
3326			return material;
3327
3328		} );
3329
3330	}
3331
3332	/** When Object3D instances are targeted by animation, they need unique names. */
3333	createUniqueName( originalName ) {
3334
3335		const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' );
3336
3337		let name = sanitizedName;
3338
3339		for ( let i = 1; this.nodeNamesUsed[ name ]; ++ i ) {
3340
3341			name = sanitizedName + '_' + i;
3342
3343		}
3344
3345		this.nodeNamesUsed[ name ] = true;
3346
3347		return name;
3348
3349	}
3350
3351	/**
3352	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
3353	 *
3354	 * Creates BufferGeometries from primitives.
3355	 *
3356	 * @param {Array<GLTF.Primitive>} primitives
3357	 * @return {Promise<Array<BufferGeometry>>}
3358	 */
3359	loadGeometries( primitives ) {
3360
3361		const parser = this;
3362		const extensions = this.extensions;
3363		const cache = this.primitiveCache;
3364
3365		function createDracoPrimitive( primitive ) {
3366
3367			return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]
3368				.decodePrimitive( primitive, parser )
3369				.then( function ( geometry ) {
3370
3371					return addPrimitiveAttributes( geometry, primitive, parser );
3372
3373				} );
3374
3375		}
3376
3377		const pending = [];
3378
3379		for ( let i = 0, il = primitives.length; i < il; i ++ ) {
3380
3381			const primitive = primitives[ i ];
3382			const cacheKey = createPrimitiveKey( primitive );
3383
3384			// See if we've already created this geometry
3385			const cached = cache[ cacheKey ];
3386
3387			if ( cached ) {
3388
3389				// Use the cached geometry if it exists
3390				pending.push( cached.promise );
3391
3392			} else {
3393
3394				let geometryPromise;
3395
3396				if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) {
3397
3398					// Use DRACO geometry if available
3399					geometryPromise = createDracoPrimitive( primitive );
3400
3401				} else {
3402
3403					// Otherwise create a new geometry
3404					geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser );
3405
3406				}
3407
3408				// Cache this geometry
3409				cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise };
3410
3411				pending.push( geometryPromise );
3412
3413			}
3414
3415		}
3416
3417		return Promise.all( pending );
3418
3419	}
3420
3421	/**
3422	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
3423	 * @param {number} meshIndex
3424	 * @return {Promise<Group|Mesh|SkinnedMesh>}
3425	 */
3426	loadMesh( meshIndex ) {
3427
3428		const parser = this;
3429		const json = this.json;
3430		const extensions = this.extensions;
3431
3432		const meshDef = json.meshes[ meshIndex ];
3433		const primitives = meshDef.primitives;
3434
3435		const pending = [];
3436
3437		for ( let i = 0, il = primitives.length; i < il; i ++ ) {
3438
3439			const material = primitives[ i ].material === undefined
3440				? createDefaultMaterial( this.cache )
3441				: this.getDependency( 'material', primitives[ i ].material );
3442
3443			pending.push( material );
3444
3445		}
3446
3447		pending.push( parser.loadGeometries( primitives ) );
3448
3449		return Promise.all( pending ).then( function ( results ) {
3450
3451			const materials = results.slice( 0, results.length - 1 );
3452			const geometries = results[ results.length - 1 ];
3453
3454			const meshes = [];
3455
3456			for ( let i = 0, il = geometries.length; i < il; i ++ ) {
3457
3458				const geometry = geometries[ i ];
3459				const primitive = primitives[ i ];
3460
3461				// 1. create Mesh
3462
3463				let mesh;
3464
3465				const material = materials[ i ];
3466
3467				if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
3468						primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
3469						primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
3470						primitive.mode === undefined ) {
3471
3472					// .isSkinnedMesh isn't in glTF spec. See ._markDefs()
3473					mesh = meshDef.isSkinnedMesh === true
3474						? new SkinnedMesh( geometry, material )
3475						: new Mesh( geometry, material );
3476
3477					if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
3478
3479						// we normalize floating point skin weight array to fix malformed assets (see #15319)
3480						// it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
3481						mesh.normalizeSkinWeights();
3482
3483					}
3484
3485					if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
3486
3487						mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleStripDrawMode );
3488
3489					} else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
3490
3491						mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleFanDrawMode );
3492
3493					}
3494
3495				} else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
3496
3497					mesh = new LineSegments( geometry, material );
3498
3499				} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
3500
3501					mesh = new Line( geometry, material );
3502
3503				} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
3504
3505					mesh = new LineLoop( geometry, material );
3506
3507				} else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
3508
3509					mesh = new Points( geometry, material );
3510
3511				} else {
3512
3513					throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
3514
3515				}
3516
3517				if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
3518
3519					updateMorphTargets( mesh, meshDef );
3520
3521				}
3522
3523				mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) );
3524
3525				assignExtrasToUserData( mesh, meshDef );
3526
3527				if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive );
3528
3529				parser.assignFinalMaterial( mesh );
3530
3531				meshes.push( mesh );
3532
3533			}
3534
3535			for ( let i = 0, il = meshes.length; i < il; i ++ ) {
3536
3537				parser.associations.set( meshes[ i ], {
3538					meshes: meshIndex,
3539					primitives: i
3540				} );
3541
3542			}
3543
3544			if ( meshes.length === 1 ) {
3545
3546				return meshes[ 0 ];
3547
3548			}
3549
3550			const group = new Group();
3551
3552			parser.associations.set( group, { meshes: meshIndex } );
3553
3554			for ( let i = 0, il = meshes.length; i < il; i ++ ) {
3555
3556				group.add( meshes[ i ] );
3557
3558			}
3559
3560			return group;
3561
3562		} );
3563
3564	}
3565
vendor: 3,060 bytes, lines 3566-3692
3566	/**
3567	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
3568	 * @param {number} cameraIndex
3569	 * @return {Promise<THREE.Camera>}
3570	 */
3571	loadCamera( cameraIndex ) {
3572
3573		let camera;
3574		const cameraDef = this.json.cameras[ cameraIndex ];
3575		const params = cameraDef[ cameraDef.type ];
3576
3577		if ( ! params ) {
3578
3579			console.warn( 'THREE.GLTFLoader: Missing camera parameters.' );
3580			return;
3581
3582		}
3583
3584		if ( cameraDef.type === 'perspective' ) {
3585
3586			camera = new PerspectiveCamera( MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 );
3587
3588		} else if ( cameraDef.type === 'orthographic' ) {
3589
3590			camera = new OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar );
3591
3592		}
3593
3594		if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name );
3595
3596		assignExtrasToUserData( camera, cameraDef );
3597
3598		return Promise.resolve( camera );
3599
3600	}
3601
3602	/**
3603	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
3604	 * @param {number} skinIndex
3605	 * @return {Promise<Skeleton>}
3606	 */
3607	loadSkin( skinIndex ) {
3608
3609		const skinDef = this.json.skins[ skinIndex ];
3610
3611		const pending = [];
3612
3613		for ( let i = 0, il = skinDef.joints.length; i < il; i ++ ) {
3614
3615			pending.push( this.getDependency( 'node', skinDef.joints[ i ] ) );
3616
3617		}
3618
3619		if ( skinDef.inverseBindMatrices !== undefined ) {
3620
3621			pending.push( this.getDependency( 'accessor', skinDef.inverseBindMatrices ) );
3622
3623		} else {
3624
3625			pending.push( null );
3626
3627		}
3628
3629		return Promise.all( pending ).then( function ( results ) {
3630
3631			const inverseBindMatrices = results.pop();
3632			const jointNodes = results;
3633
3634			const bones = [];
3635			const boneInverses = [];
3636
3637			for ( let i = 0, il = jointNodes.length; i < il; i ++ ) {
3638
3639				const jointNode = jointNodes[ i ];
3640
3641				if ( jointNode ) {
3642
3643					bones.push( jointNode );
3644
3645					const mat = new Matrix4();
3646
3647					if ( inverseBindMatrices !== null ) {
3648
3649						mat.fromArray( inverseBindMatrices.array, i * 16 );
3650
3651					}
3652
3653					boneInverses.push( mat );
3654
3655				} else {
3656
3657					console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinDef.joints[ i ] );
3658
3659				}
3660
3661			}
3662
3663			return new Skeleton( bones, boneInverses );
3664
3665		} );
3666
3667	}
3668
3669	/**
3670	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations
3671	 * @param {number} animationIndex
3672	 * @return {Promise<AnimationClip>}
3673	 */
3674	loadAnimation( animationIndex ) {
3675
3676		const json = this.json;
3677
3678		const animationDef = json.animations[ animationIndex ];
3679
3680		const pendingNodes = [];
3681		const pendingInputAccessors = [];
3682		const pendingOutputAccessors = [];
3683		const pendingSamplers = [];
3684		const pendingTargets = [];
3685
3686		for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) {
3687
3688			const channel = animationDef.channels[ i ];
3689			const sampler = animationDef.samplers[ channel.sampler ];
3690			const target = channel.target;
3691			const name = target.node;
3692			const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input;
3693			const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output;
3694
3695			pendingNodes.push( this.getDependency( 'node', name ) );
3696			pendingInputAccessors.push( this.getDependency( 'accessor', input ) );
3697			pendingOutputAccessors.push( this.getDependency( 'accessor', output ) );
3698			pendingSamplers.push( sampler );
3699			pendingTargets.push( target );
3700
3701		}
3702
3703		return Promise.all( [
3704
3705			Promise.all( pendingNodes ),
3706			Promise.all( pendingInputAccessors ),
3707			Promise.all( pendingOutputAccessors ),
3708			Promise.all( pendingSamplers ),
3709			Promise.all( pendingTargets )
3710
3711		] ).then( function ( dependencies ) {
3712
3713			const nodes = dependencies[ 0 ];
3714			const inputAccessors = dependencies[ 1 ];
3715			const outputAccessors = dependencies[ 2 ];
3716			const samplers = dependencies[ 3 ];
3717			const targets = dependencies[ 4 ];
3718
3719			const tracks = [];
3720
3721			for ( let i = 0, il = nodes.length; i < il; i ++ ) {
3722
3723				const node = nodes[ i ];
3724				const inputAccessor = inputAccessors[ i ];
3725				const outputAccessor = outputAccessors[ i ];
3726				const sampler = samplers[ i ];
3727				const target = targets[ i ];
3728
3729				if ( node === undefined ) continue;
3730
3731				node.updateMatrix();
3732
3733				let TypedKeyframeTrack;
3734
3735				switch ( PATH_PROPERTIES[ target.path ] ) {
3736
3737					case PATH_PROPERTIES.weights:
3738
3739						TypedKeyframeTrack = NumberKeyframeTrack;
3740						break;
3741
3742					case PATH_PROPERTIES.rotation:
3743
3744						TypedKeyframeTrack = QuaternionKeyframeTrack;
3745						break;
3746
3747					case PATH_PROPERTIES.position:
3748					case PATH_PROPERTIES.scale:
3749					default:
3750
3751						TypedKeyframeTrack = VectorKeyframeTrack;
3752						break;
3753
3754				}
3755
3756				const targetName = node.name ? node.name : node.uuid;
3757
3758				const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear;
3759
3760				const targetNames = [];
3761
3762				if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) {
3763
3764					node.traverse( function ( object ) {
3765
3766						if ( object.morphTargetInfluences ) {
3767
3768							targetNames.push( object.name ? object.name : object.uuid );
3769
3770						}
3771
3772					} );
3773
3774				} else {
3775
3776					targetNames.push( targetName );
3777
3778				}
3779
3780				let outputArray = outputAccessor.array;
3781
3782				if ( outputAccessor.normalized ) {
3783
3784					const scale = getNormalizedComponentScale( outputArray.constructor );
3785					const scaled = new Float32Array( outputArray.length );
3786
3787					for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) {
3788
3789						scaled[ j ] = outputArray[ j ] * scale;
3790
3791					}
3792
3793					outputArray = scaled;
3794
3795				}
3796
3797				for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) {
3798
3799					const track = new TypedKeyframeTrack(
3800						targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ],
3801						inputAccessor.array,
3802						outputArray,
3803						interpolation
3804					);
3805
3806					// Override interpolation with custom factory method.
3807					if ( sampler.interpolation === 'CUBICSPLINE' ) {
3808
3809						track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) {
3810
3811							// A CUBICSPLINE keyframe in glTF has three output values for each input value,
3812							// representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize()
3813							// must be divided by three to get the interpolant's sampleSize argument.
3814
3815							const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant;
3816
3817							return new interpolantType( this.times, this.values, this.getValueSize() / 3, result );
3818
3819						};
3820
3821						// Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants.
3822						track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true;
3823
3824					}
3825
3826					tracks.push( track );
3827
3828				}
3829
3830			}
3831
3832			const name = animationDef.name ? animationDef.name : 'animation_' + animationIndex;
3833
3834			return new AnimationClip( name, undefined, tracks );
3835
3836		} );
3837
3838	}
3839
3840	createNodeMesh( nodeIndex ) {
3841
3842		const json = this.json;
3843		const parser = this;
3844		const nodeDef = json.nodes[ nodeIndex ];
3845
3846		if ( nodeDef.mesh === undefined ) return null;
3847
3848		return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) {
3849
3850			const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh );
3851
3852			// if weights are provided on the node, override weights on the mesh.
3853			if ( nodeDef.weights !== undefined ) {
3854
3855				node.traverse( function ( o ) {
3856
3857					if ( ! o.isMesh ) return;
3858
3859					for ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) {
3860
3861						o.morphTargetInfluences[ i ] = nodeDef.weights[ i ];
3862
3863					}
3864
3865				} );
3866
3867			}
3868
3869			return node;
3870
3871		} );
3872
3873	}
3874
3875	/**
3876	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy
3877	 * @param {number} nodeIndex
3878	 * @return {Promise<Object3D>}
3879	 */
3880	loadNode( nodeIndex ) {
3881
3882		const json = this.json;
3883		const extensions = this.extensions;
3884		const parser = this;
3885
3886		const nodeDef = json.nodes[ nodeIndex ];
3887
3888		// reserve node's name before its dependencies, so the root has the intended name.
3889		const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : '';
3890
3891		return ( function () {
3892
3893			const objectPending = [];
3894
3895			const meshPromise = parser._invokeOne( function ( ext ) {
3896
3897				return ext.createNodeMesh && ext.createNodeMesh( nodeIndex );
3898
3899			} );
3900
3901			if ( meshPromise ) {
3902
3903				objectPending.push( meshPromise );
3904
3905			}
3906
3907			if ( nodeDef.camera !== undefined ) {
3908
3909				objectPending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) {
3910
3911					return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera );
3912
3913				} ) );
3914
3915			}
3916
3917			parser._invokeAll( function ( ext ) {
3918
3919				return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex );
3920
3921			} ).forEach( function ( promise ) {
3922
3923				objectPending.push( promise );
3924
3925			} );
3926
3927			const childPending = [];
3928			const childrenDef = nodeDef.children || [];
3929
3930			for ( let i = 0, il = childrenDef.length; i < il; i ++ ) {
3931
3932				childPending.push( parser.getDependency( 'node', childrenDef[ i ] ) );
3933
3934			}
3935
3936			const skeletonPending = nodeDef.skin === undefined
3937				? Promise.resolve( null )
3938				: parser.getDependency( 'skin', nodeDef.skin );
3939
3940			return Promise.all( [
3941				Promise.all( objectPending ),
3942				Promise.all( childPending ),
3943				skeletonPending
3944			] );
3945
3946		}() ).then( function ( results ) {
3947
3948			const objects = results[ 0 ];
3949			const children = results[ 1 ];
3950			const skeleton = results[ 2 ];
3951
3952			let node;
3953
3954			// .isBone isn't in glTF spec. See ._markDefs
3955			if ( nodeDef.isBone === true ) {
3956
3957				node = new Bone();
3958
3959			} else if ( objects.length > 1 ) {
3960
3961				node = new Group();
3962
3963			} else if ( objects.length === 1 ) {
3964
3965				node = objects[ 0 ];
3966
3967			} else {
3968
3969				node = new Object3D();
3970
3971			}
3972
3973			if ( node !== objects[ 0 ] ) {
3974
3975				for ( let i = 0, il = objects.length; i < il; i ++ ) {
3976
3977					node.add( objects[ i ] );
3978
3979				}
3980
3981			}
3982
3983			if ( nodeDef.name ) {
3984
3985				node.userData.name = nodeDef.name;
3986				node.name = nodeName;
3987
3988			}
3989
3990			assignExtrasToUserData( node, nodeDef );
3991
3992			if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef );
3993
3994			if ( nodeDef.matrix !== undefined ) {
3995
3996				const matrix = new Matrix4();
3997				matrix.fromArray( nodeDef.matrix );
3998				node.applyMatrix4( matrix );
3999
4000			} else {
4001
4002				if ( nodeDef.translation !== undefined ) {
4003
4004					node.position.fromArray( nodeDef.translation );
4005
4006				}
4007
4008				if ( nodeDef.rotation !== undefined ) {
4009
4010					node.quaternion.fromArray( nodeDef.rotation );
4011
4012				}
4013
4014				if ( nodeDef.scale !== undefined ) {
4015
4016					node.scale.fromArray( nodeDef.scale );
4017
4018				}
4019
4020			}
4021
4022			if ( ! parser.associations.has( node ) ) {
4023
4024				parser.associations.set( node, {} );
4025
4026			}
4027
4028			parser.associations.get( node ).nodes = nodeIndex;
4029
4030			if ( skeleton !== null ) {
4031
4032				// This full traverse should be fine because
4033				// child glTF nodes have not been added to this node yet.
4034				node.traverse( function ( mesh ) {
4035
4036					if ( ! mesh.isSkinnedMesh ) return;
4037
4038					mesh.bind( skeleton, _identityMatrix );
4039
4040				} );
4041
4042			}
4043
4044			for ( let i = 0, il = children.length; i < il; i ++ ) {
4045
4046				node.add( children[ i ] );
4047
4048			}
4049
4050			return node;
4051
4052		} );
4053
4054	}
4055
4056	/**
4057	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes
4058	 * @param {number} sceneIndex
4059	 * @return {Promise<Group>}
4060	 */
4061	loadScene( sceneIndex ) {
4062
4063		const extensions = this.extensions;
4064		const sceneDef = this.json.scenes[ sceneIndex ];
4065		const parser = this;
4066
4067		// Loader returns Group, not Scene.
4068		// See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172
4069		const scene = new Group();
4070		if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name );
4071
4072		assignExtrasToUserData( scene, sceneDef );
4073
4074		if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef );
4075
4076		const nodeIds = sceneDef.nodes || [];
4077
4078		const pending = [];
4079
4080		for ( let i = 0, il = nodeIds.length; i < il; i ++ ) {
4081
4082			pending.push( parser.getDependency( 'node', nodeIds[ i ] ) );
4083
4084		}
4085
4086		return Promise.all( pending ).then( function ( nodes ) {
4087
4088			for ( let i = 0, il = nodes.length; i < il; i ++ ) {
4089
4090				scene.add( nodes[ i ] );
4091
4092			}
4093
4094			// Removes dangling associations, associations that reference a node that
4095			// didn't make it into the scene.
4096			const reduceAssociations = ( node ) => {
4097
4098				const reducedAssociations = new Map();
4099
4100				for ( const [ key, value ] of parser.associations ) {
4101
4102					if ( key instanceof Material || key instanceof Texture ) {
4103
vendor: 4,281 bytes, lines 4104-4280
4104						reducedAssociations.set( key, value );
4105
4106					}
4107
4108				}
4109
4110				node.traverse( ( node ) => {
4111
4112					const mappings = parser.associations.get( node );
4113
4114					if ( mappings != null ) {
4115
4116						reducedAssociations.set( node, mappings );
4117
4118					}
4119
4120				} );
4121
4122				return reducedAssociations;
4123
4124			};
4125
4126			parser.associations = reduceAssociations( scene );
4127
4128			return scene;
4129
4130		} );
4131
4132	}
4133
4134}
4135
4136/**
4137 * @param {BufferGeometry} geometry
4138 * @param {GLTF.Primitive} primitiveDef
4139 * @param {GLTFParser} parser
4140 */
4141function computeBounds( geometry, primitiveDef, parser ) {
4142
4143	const attributes = primitiveDef.attributes;
4144
4145	const box = new Box3();
4146
4147	if ( attributes.POSITION !== undefined ) {
4148
4149		const accessor = parser.json.accessors[ attributes.POSITION ];
4150
4151		const min = accessor.min;
4152		const max = accessor.max;
4153
4154		// glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
4155
4156		if ( min !== undefined && max !== undefined ) {
4157
4158			box.set(
4159				new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ),
4160				new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] )
4161			);
4162
4163			if ( accessor.normalized ) {
4164
4165				const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] );
4166				box.min.multiplyScalar( boxScale );
4167				box.max.multiplyScalar( boxScale );
4168
4169			}
4170
4171		} else {
4172
4173			console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );
4174
4175			return;
4176
4177		}
4178
4179	} else {
4180
4181		return;
4182
4183	}
4184
4185	const targets = primitiveDef.targets;
4186
4187	if ( targets !== undefined ) {
4188
4189		const maxDisplacement = new Vector3();
4190		const vector = new Vector3();
4191
4192		for ( let i = 0, il = targets.length; i < il; i ++ ) {
4193
4194			const target = targets[ i ];
4195
4196			if ( target.POSITION !== undefined ) {
4197
4198				const accessor = parser.json.accessors[ target.POSITION ];
4199				const min = accessor.min;
4200				const max = accessor.max;
4201
4202				// glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
4203
4204				if ( min !== undefined && max !== undefined ) {
4205
4206					// we need to get max of absolute components because target weight is [-1,1]
4207					vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) );
4208					vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) );
4209					vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) );
4210
4211
4212					if ( accessor.normalized ) {
4213
4214						const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] );
4215						vector.multiplyScalar( boxScale );
4216
4217					}
4218
4219					// Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative
4220					// to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets
4221					// are used to implement key-frame animations and as such only two are active at a time - this results in very large
4222					// boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size.
4223					maxDisplacement.max( vector );
4224
4225				} else {
4226
4227					console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );
4228
4229				}
4230
4231			}
4232
4233		}
4234
4235		// As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets.
4236		box.expandByVector( maxDisplacement );
4237
4238	}
4239
4240	geometry.boundingBox = box;
4241
4242	const sphere = new Sphere();
4243
4244	box.getCenter( sphere.center );
4245	sphere.radius = box.min.distanceTo( box.max ) / 2;
4246
4247	geometry.boundingSphere = sphere;
4248
4249}
4250
4251/**
4252 * @param {BufferGeometry} geometry
4253 * @param {GLTF.Primitive} primitiveDef
4254 * @param {GLTFParser} parser
4255 * @return {Promise<BufferGeometry>}
4256 */
4257function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
4258
4259	const attributes = primitiveDef.attributes;
4260
4261	const pending = [];
4262
4263	function assignAttributeAccessor( accessorIndex, attributeName ) {
4264
4265		return parser.getDependency( 'accessor', accessorIndex )
4266			.then( function ( accessor ) {
4267
4268				geometry.setAttribute( attributeName, accessor );
4269
4270			} );
4271
4272	}
4273
4274	for ( const gltfAttributeName in attributes ) {
4275
4276		const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();
4277
4278		// Skip attributes already provided by e.g. Draco extension.
4279		if ( threeAttributeName in geometry.attributes ) continue;
4280
4281		pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );
4282
4283	}
4284
4285	if ( primitiveDef.indices !== undefined && ! geometry.index ) {
4286
4287		const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {
4288
4289			geometry.setIndex( accessor );
4290
4291		} );
4292
4293		pending.push( accessor );
4294
4295	}
4296
4297	assignExtrasToUserData( geometry, primitiveDef );
4298
4299	computeBounds( geometry, primitiveDef, parser );
4300
4301	return Promise.all( pending ).then( function () {
4302
4303		return primitiveDef.targets !== undefined
4304			? addMorphTargets( geometry, primitiveDef.targets, parser )
4305			: geometry;
4306
4307	} );
4308
4309}
4310
4311export { GLTFLoader };

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.