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.