PageSourceSearch

https://www.foundsurface.com/studio/assets/GLTFLoader.js

js foundsurface.com collected 2026-10-04 02:08:07 UTC 110,022 bytes, 4,725 lines download raw bytes

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