Spaces:
Running on Zero
Running on Zero
| import { | |
| AnimationClip, | |
| Bone, | |
| Box3, | |
| BufferAttribute, | |
| BufferGeometry, | |
| ClampToEdgeWrapping, | |
| Color, | |
| ColorManagement, | |
| DirectionalLight, | |
| DoubleSide, | |
| FileLoader, | |
| FrontSide, | |
| Group, | |
| ImageBitmapLoader, | |
| InstancedMesh, | |
| InterleavedBuffer, | |
| InterleavedBufferAttribute, | |
| Interpolant, | |
| InterpolateDiscrete, | |
| InterpolateLinear, | |
| Line, | |
| LineBasicMaterial, | |
| LineLoop, | |
| LineSegments, | |
| LinearFilter, | |
| LinearMipmapLinearFilter, | |
| LinearMipmapNearestFilter, | |
| LinearSRGBColorSpace, | |
| Loader, | |
| LoaderUtils, | |
| Material, | |
| MathUtils, | |
| Matrix4, | |
| Mesh, | |
| MeshBasicMaterial, | |
| MeshPhysicalMaterial, | |
| MeshStandardMaterial, | |
| MirroredRepeatWrapping, | |
| NearestFilter, | |
| NearestMipmapLinearFilter, | |
| NearestMipmapNearestFilter, | |
| NumberKeyframeTrack, | |
| Object3D, | |
| OrthographicCamera, | |
| PerspectiveCamera, | |
| PointLight, | |
| Points, | |
| PointsMaterial, | |
| PropertyBinding, | |
| Quaternion, | |
| QuaternionKeyframeTrack, | |
| RepeatWrapping, | |
| Skeleton, | |
| SkinnedMesh, | |
| Sphere, | |
| SpotLight, | |
| Texture, | |
| TextureLoader, | |
| TriangleFanDrawMode, | |
| TriangleStripDrawMode, | |
| Vector2, | |
| Vector3, | |
| VectorKeyframeTrack, | |
| SRGBColorSpace, | |
| InstancedBufferAttribute | |
| } from 'three'; | |
| import { toTrianglesDrawMode } from '../utils/BufferGeometryUtils.js'; | |
| class GLTFLoader extends Loader { | |
| constructor( manager ) { | |
| super( manager ); | |
| this.dracoLoader = null; | |
| this.ktx2Loader = null; | |
| this.meshoptDecoder = null; | |
| this.pluginCallbacks = []; | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsClearcoatExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFTextureBasisUExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFTextureWebPExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFTextureAVIFExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsSheenExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsTransmissionExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsVolumeExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsIorExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsEmissiveStrengthExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsSpecularExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsIridescenceExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMaterialsAnisotropyExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFLightsExtension( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMeshoptCompression( parser ); | |
| } ); | |
| this.register( function ( parser ) { | |
| return new GLTFMeshGpuInstancing( parser ); | |
| } ); | |
| } | |
| load( url, onLoad, onProgress, onError ) { | |
| const scope = this; | |
| let resourcePath; | |
| if ( this.resourcePath !== '' ) { | |
| resourcePath = this.resourcePath; | |
| } else if ( this.path !== '' ) { | |
| resourcePath = this.path; | |
| } else { | |
| resourcePath = LoaderUtils.extractUrlBase( url ); | |
| } | |
| // Tells the LoadingManager to track an extra item, which resolves after | |
| // the model is fully loaded. This means the count of items loaded will | |
| // be incorrect, but ensures manager.onLoad() does not fire early. | |
| this.manager.itemStart( url ); | |
| const _onError = function ( e ) { | |
| if ( onError ) { | |
| onError( e ); | |
| } else { | |
| console.error( e ); | |
| } | |
| scope.manager.itemError( url ); | |
| scope.manager.itemEnd( url ); | |
| }; | |
| const loader = new FileLoader( this.manager ); | |
| loader.setPath( this.path ); | |
| loader.setResponseType( 'arraybuffer' ); | |
| loader.setRequestHeader( this.requestHeader ); | |
| loader.setWithCredentials( this.withCredentials ); | |
| loader.load( url, function ( data ) { | |
| try { | |
| scope.parse( data, resourcePath, function ( gltf ) { | |
| onLoad( gltf ); | |
| scope.manager.itemEnd( url ); | |
| }, _onError ); | |
| } catch ( e ) { | |
| _onError( e ); | |
| } | |
| }, onProgress, _onError ); | |
| } | |
| setDRACOLoader( dracoLoader ) { | |
| this.dracoLoader = dracoLoader; | |
| return this; | |
| } | |
| setDDSLoader() { | |
| throw new Error( | |
| 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' | |
| ); | |
| } | |
| setKTX2Loader( ktx2Loader ) { | |
| this.ktx2Loader = ktx2Loader; | |
| return this; | |
| } | |
| setMeshoptDecoder( meshoptDecoder ) { | |
| this.meshoptDecoder = meshoptDecoder; | |
| return this; | |
| } | |
| register( callback ) { | |
| if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { | |
| this.pluginCallbacks.push( callback ); | |
| } | |
| return this; | |
| } | |
| unregister( callback ) { | |
| if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { | |
| this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); | |
| } | |
| return this; | |
| } | |
| parse( data, path, onLoad, onError ) { | |
| let json; | |
| const extensions = {}; | |
| const plugins = {}; | |
| const textDecoder = new TextDecoder(); | |
| if ( typeof data === 'string' ) { | |
| json = JSON.parse( data ); | |
| } else if ( data instanceof ArrayBuffer ) { | |
| const magic = textDecoder.decode( new Uint8Array( data, 0, 4 ) ); | |
| if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { | |
| try { | |
| extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); | |
| } catch ( error ) { | |
| if ( onError ) onError( error ); | |
| return; | |
| } | |
| json = JSON.parse( extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content ); | |
| } else { | |
| json = JSON.parse( textDecoder.decode( data ) ); | |
| } | |
| } else { | |
| json = data; | |
| } | |
| if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { | |
| if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); | |
| return; | |
| } | |
| const parser = new GLTFParser( json, { | |
| path: path || this.resourcePath || '', | |
| crossOrigin: this.crossOrigin, | |
| requestHeader: this.requestHeader, | |
| manager: this.manager, | |
| ktx2Loader: this.ktx2Loader, | |
| meshoptDecoder: this.meshoptDecoder | |
| } ); | |
| parser.fileLoader.setRequestHeader( this.requestHeader ); | |
| for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { | |
| const plugin = this.pluginCallbacks[ i ]( parser ); | |
| if ( ! plugin.name ) console.error( 'THREE.GLTFLoader: Invalid plugin found: missing name' ); | |
| plugins[ plugin.name ] = plugin; | |
| // Workaround to avoid determining as unknown extension | |
| // in addUnknownExtensionsToUserData(). | |
| // Remove this workaround if we move all the existing | |
| // extension handlers to plugin system | |
| extensions[ plugin.name ] = true; | |
| } | |
| if ( json.extensionsUsed ) { | |
| for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { | |
| const extensionName = json.extensionsUsed[ i ]; | |
| const extensionsRequired = json.extensionsRequired || []; | |
| switch ( extensionName ) { | |
| case EXTENSIONS.KHR_MATERIALS_UNLIT: | |
| extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); | |
| break; | |
| case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: | |
| extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); | |
| break; | |
| case EXTENSIONS.KHR_TEXTURE_TRANSFORM: | |
| extensions[ extensionName ] = new GLTFTextureTransformExtension(); | |
| break; | |
| case EXTENSIONS.KHR_MESH_QUANTIZATION: | |
| extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); | |
| break; | |
| default: | |
| if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) { | |
| console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); | |
| } | |
| } | |
| } | |
| } | |
| parser.setExtensions( extensions ); | |
| parser.setPlugins( plugins ); | |
| parser.parse( onLoad, onError ); | |
| } | |
| parseAsync( data, path ) { | |
| const scope = this; | |
| return new Promise( function ( resolve, reject ) { | |
| scope.parse( data, path, resolve, reject ); | |
| } ); | |
| } | |
| } | |
| /* GLTFREGISTRY */ | |
| function GLTFRegistry() { | |
| let objects = {}; | |
| return { | |
| get: function ( key ) { | |
| return objects[ key ]; | |
| }, | |
| add: function ( key, object ) { | |
| objects[ key ] = object; | |
| }, | |
| remove: function ( key ) { | |
| delete objects[ key ]; | |
| }, | |
| removeAll: function () { | |
| objects = {}; | |
| } | |
| }; | |
| } | |
| /*********************************/ | |
| /********** EXTENSIONS ***********/ | |
| /*********************************/ | |
| const EXTENSIONS = { | |
| KHR_BINARY_GLTF: 'KHR_binary_glTF', | |
| KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', | |
| KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', | |
| KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', | |
| KHR_MATERIALS_IOR: 'KHR_materials_ior', | |
| KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', | |
| KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', | |
| KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', | |
| KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence', | |
| KHR_MATERIALS_ANISOTROPY: 'KHR_materials_anisotropy', | |
| KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', | |
| KHR_MATERIALS_VOLUME: 'KHR_materials_volume', | |
| KHR_TEXTURE_BASISU: 'KHR_texture_basisu', | |
| KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', | |
| KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', | |
| KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength', | |
| EXT_TEXTURE_WEBP: 'EXT_texture_webp', | |
| EXT_TEXTURE_AVIF: 'EXT_texture_avif', | |
| EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression', | |
| EXT_MESH_GPU_INSTANCING: 'EXT_mesh_gpu_instancing' | |
| }; | |
| /** | |
| * Punctual Lights Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual | |
| */ | |
| class GLTFLightsExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; | |
| // Object3D instance caches | |
| this.cache = { refs: {}, uses: {} }; | |
| } | |
| _markDefs() { | |
| const parser = this.parser; | |
| const nodeDefs = this.parser.json.nodes || []; | |
| for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { | |
| const nodeDef = nodeDefs[ nodeIndex ]; | |
| if ( nodeDef.extensions | |
| && nodeDef.extensions[ this.name ] | |
| && nodeDef.extensions[ this.name ].light !== undefined ) { | |
| parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); | |
| } | |
| } | |
| } | |
| _loadLight( lightIndex ) { | |
| const parser = this.parser; | |
| const cacheKey = 'light:' + lightIndex; | |
| let dependency = parser.cache.get( cacheKey ); | |
| if ( dependency ) return dependency; | |
| const json = parser.json; | |
| const extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; | |
| const lightDefs = extensions.lights || []; | |
| const lightDef = lightDefs[ lightIndex ]; | |
| let lightNode; | |
| const color = new Color( 0xffffff ); | |
| if ( lightDef.color !== undefined ) color.setRGB( lightDef.color[ 0 ], lightDef.color[ 1 ], lightDef.color[ 2 ], LinearSRGBColorSpace ); | |
| const range = lightDef.range !== undefined ? lightDef.range : 0; | |
| switch ( lightDef.type ) { | |
| case 'directional': | |
| lightNode = new DirectionalLight( color ); | |
| lightNode.target.position.set( 0, 0, - 1 ); | |
| lightNode.add( lightNode.target ); | |
| break; | |
| case 'point': | |
| lightNode = new PointLight( color ); | |
| lightNode.distance = range; | |
| break; | |
| case 'spot': | |
| lightNode = new SpotLight( color ); | |
| lightNode.distance = range; | |
| // Handle spotlight properties. | |
| lightDef.spot = lightDef.spot || {}; | |
| lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; | |
| lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; | |
| lightNode.angle = lightDef.spot.outerConeAngle; | |
| lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; | |
| lightNode.target.position.set( 0, 0, - 1 ); | |
| lightNode.add( lightNode.target ); | |
| break; | |
| default: | |
| throw new Error( 'THREE.GLTFLoader: Unexpected light type: ' + lightDef.type ); | |
| } | |
| // Some lights (e.g. spot) default to a position other than the origin. Reset the position | |
| // here, because node-level parsing will only override position if explicitly specified. | |
| lightNode.position.set( 0, 0, 0 ); | |
| lightNode.decay = 2; | |
| assignExtrasToUserData( lightNode, lightDef ); | |
| if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; | |
| lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) ); | |
| dependency = Promise.resolve( lightNode ); | |
| parser.cache.add( cacheKey, dependency ); | |
| return dependency; | |
| } | |
| getDependency( type, index ) { | |
| if ( type !== 'light' ) return; | |
| return this._loadLight( index ); | |
| } | |
| createNodeAttachment( nodeIndex ) { | |
| const self = this; | |
| const parser = this.parser; | |
| const json = parser.json; | |
| const nodeDef = json.nodes[ nodeIndex ]; | |
| const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; | |
| const lightIndex = lightDef.light; | |
| if ( lightIndex === undefined ) return null; | |
| return this._loadLight( lightIndex ).then( function ( light ) { | |
| return parser._getNodeRef( self.cache, lightIndex, light ); | |
| } ); | |
| } | |
| } | |
| /** | |
| * Unlit Materials Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit | |
| */ | |
| class GLTFMaterialsUnlitExtension { | |
| constructor() { | |
| this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; | |
| } | |
| getMaterialType() { | |
| return MeshBasicMaterial; | |
| } | |
| extendParams( materialParams, materialDef, parser ) { | |
| const pending = []; | |
| materialParams.color = new Color( 1.0, 1.0, 1.0 ); | |
| materialParams.opacity = 1.0; | |
| const metallicRoughness = materialDef.pbrMetallicRoughness; | |
| if ( metallicRoughness ) { | |
| if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { | |
| const array = metallicRoughness.baseColorFactor; | |
| materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace ); | |
| materialParams.opacity = array[ 3 ]; | |
| } | |
| if ( metallicRoughness.baseColorTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, SRGBColorSpace ) ); | |
| } | |
| } | |
| return Promise.all( pending ); | |
| } | |
| } | |
| /** | |
| * Materials Emissive Strength Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md | |
| */ | |
| class GLTFMaterialsEmissiveStrengthExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const emissiveStrength = materialDef.extensions[ this.name ].emissiveStrength; | |
| if ( emissiveStrength !== undefined ) { | |
| materialParams.emissiveIntensity = emissiveStrength; | |
| } | |
| return Promise.resolve(); | |
| } | |
| } | |
| /** | |
| * Clearcoat Materials Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat | |
| */ | |
| class GLTFMaterialsClearcoatExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; | |
| } | |
| getMaterialType( materialIndex ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; | |
| return MeshPhysicalMaterial; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const pending = []; | |
| const extension = materialDef.extensions[ this.name ]; | |
| if ( extension.clearcoatFactor !== undefined ) { | |
| materialParams.clearcoat = extension.clearcoatFactor; | |
| } | |
| if ( extension.clearcoatTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) ); | |
| } | |
| if ( extension.clearcoatRoughnessFactor !== undefined ) { | |
| materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; | |
| } | |
| if ( extension.clearcoatRoughnessTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) ); | |
| } | |
| if ( extension.clearcoatNormalTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) ); | |
| if ( extension.clearcoatNormalTexture.scale !== undefined ) { | |
| const scale = extension.clearcoatNormalTexture.scale; | |
| materialParams.clearcoatNormalScale = new Vector2( scale, scale ); | |
| } | |
| } | |
| return Promise.all( pending ); | |
| } | |
| } | |
| /** | |
| * Iridescence Materials Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence | |
| */ | |
| class GLTFMaterialsIridescenceExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; | |
| } | |
| getMaterialType( materialIndex ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; | |
| return MeshPhysicalMaterial; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const pending = []; | |
| const extension = materialDef.extensions[ this.name ]; | |
| if ( extension.iridescenceFactor !== undefined ) { | |
| materialParams.iridescence = extension.iridescenceFactor; | |
| } | |
| if ( extension.iridescenceTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) ); | |
| } | |
| if ( extension.iridescenceIor !== undefined ) { | |
| materialParams.iridescenceIOR = extension.iridescenceIor; | |
| } | |
| if ( materialParams.iridescenceThicknessRange === undefined ) { | |
| materialParams.iridescenceThicknessRange = [ 100, 400 ]; | |
| } | |
| if ( extension.iridescenceThicknessMinimum !== undefined ) { | |
| materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum; | |
| } | |
| if ( extension.iridescenceThicknessMaximum !== undefined ) { | |
| materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum; | |
| } | |
| if ( extension.iridescenceThicknessTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) ); | |
| } | |
| return Promise.all( pending ); | |
| } | |
| } | |
| /** | |
| * Sheen Materials Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen | |
| */ | |
| class GLTFMaterialsSheenExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; | |
| } | |
| getMaterialType( materialIndex ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; | |
| return MeshPhysicalMaterial; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const pending = []; | |
| materialParams.sheenColor = new Color( 0, 0, 0 ); | |
| materialParams.sheenRoughness = 0; | |
| materialParams.sheen = 1; | |
| const extension = materialDef.extensions[ this.name ]; | |
| if ( extension.sheenColorFactor !== undefined ) { | |
| const colorFactor = extension.sheenColorFactor; | |
| materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor[ 2 ], LinearSRGBColorSpace ); | |
| } | |
| if ( extension.sheenRoughnessFactor !== undefined ) { | |
| materialParams.sheenRoughness = extension.sheenRoughnessFactor; | |
| } | |
| if ( extension.sheenColorTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, SRGBColorSpace ) ); | |
| } | |
| if ( extension.sheenRoughnessTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) ); | |
| } | |
| return Promise.all( pending ); | |
| } | |
| } | |
| /** | |
| * Transmission Materials Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission | |
| * Draft: https://github.com/KhronosGroup/glTF/pull/1698 | |
| */ | |
| class GLTFMaterialsTransmissionExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; | |
| } | |
| getMaterialType( materialIndex ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; | |
| return MeshPhysicalMaterial; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const pending = []; | |
| const extension = materialDef.extensions[ this.name ]; | |
| if ( extension.transmissionFactor !== undefined ) { | |
| materialParams.transmission = extension.transmissionFactor; | |
| } | |
| if ( extension.transmissionTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) ); | |
| } | |
| return Promise.all( pending ); | |
| } | |
| } | |
| /** | |
| * Materials Volume Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume | |
| */ | |
| class GLTFMaterialsVolumeExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; | |
| } | |
| getMaterialType( materialIndex ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; | |
| return MeshPhysicalMaterial; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const pending = []; | |
| const extension = materialDef.extensions[ this.name ]; | |
| materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0; | |
| if ( extension.thicknessTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) ); | |
| } | |
| materialParams.attenuationDistance = extension.attenuationDistance || Infinity; | |
| const colorArray = extension.attenuationColor || [ 1, 1, 1 ]; | |
| materialParams.attenuationColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace ); | |
| return Promise.all( pending ); | |
| } | |
| } | |
| /** | |
| * Materials ior Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior | |
| */ | |
| class GLTFMaterialsIorExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_IOR; | |
| } | |
| getMaterialType( materialIndex ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; | |
| return MeshPhysicalMaterial; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const extension = materialDef.extensions[ this.name ]; | |
| materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5; | |
| return Promise.resolve(); | |
| } | |
| } | |
| /** | |
| * Materials specular Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular | |
| */ | |
| class GLTFMaterialsSpecularExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; | |
| } | |
| getMaterialType( materialIndex ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; | |
| return MeshPhysicalMaterial; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const pending = []; | |
| const extension = materialDef.extensions[ this.name ]; | |
| materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0; | |
| if ( extension.specularTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) ); | |
| } | |
| const colorArray = extension.specularColorFactor || [ 1, 1, 1 ]; | |
| materialParams.specularColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace ); | |
| if ( extension.specularColorTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, SRGBColorSpace ) ); | |
| } | |
| return Promise.all( pending ); | |
| } | |
| } | |
| /** | |
| * Materials anisotropy Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy | |
| */ | |
| class GLTFMaterialsAnisotropyExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_MATERIALS_ANISOTROPY; | |
| } | |
| getMaterialType( materialIndex ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; | |
| return MeshPhysicalMaterial; | |
| } | |
| extendMaterialParams( materialIndex, materialParams ) { | |
| const parser = this.parser; | |
| const materialDef = parser.json.materials[ materialIndex ]; | |
| if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { | |
| return Promise.resolve(); | |
| } | |
| const pending = []; | |
| const extension = materialDef.extensions[ this.name ]; | |
| if ( extension.anisotropyStrength !== undefined ) { | |
| materialParams.anisotropy = extension.anisotropyStrength; | |
| } | |
| if ( extension.anisotropyRotation !== undefined ) { | |
| materialParams.anisotropyRotation = extension.anisotropyRotation; | |
| } | |
| if ( extension.anisotropyTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'anisotropyMap', extension.anisotropyTexture ) ); | |
| } | |
| return Promise.all( pending ); | |
| } | |
| } | |
| /** | |
| * BasisU Texture Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu | |
| */ | |
| class GLTFTextureBasisUExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.KHR_TEXTURE_BASISU; | |
| } | |
| loadTexture( textureIndex ) { | |
| const parser = this.parser; | |
| const json = parser.json; | |
| const textureDef = json.textures[ textureIndex ]; | |
| if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { | |
| return null; | |
| } | |
| const extension = textureDef.extensions[ this.name ]; | |
| const loader = parser.options.ktx2Loader; | |
| if ( ! loader ) { | |
| if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { | |
| throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' ); | |
| } else { | |
| // Assumes that the extension is optional and that a fallback texture is present | |
| return null; | |
| } | |
| } | |
| return parser.loadTextureImage( textureIndex, extension.source, loader ); | |
| } | |
| } | |
| /** | |
| * WebP Texture Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp | |
| */ | |
| class GLTFTextureWebPExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.EXT_TEXTURE_WEBP; | |
| this.isSupported = null; | |
| } | |
| loadTexture( textureIndex ) { | |
| const name = this.name; | |
| const parser = this.parser; | |
| const json = parser.json; | |
| const textureDef = json.textures[ textureIndex ]; | |
| if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { | |
| return null; | |
| } | |
| const extension = textureDef.extensions[ name ]; | |
| const source = json.images[ extension.source ]; | |
| let loader = parser.textureLoader; | |
| if ( source.uri ) { | |
| const handler = parser.options.manager.getHandler( source.uri ); | |
| if ( handler !== null ) loader = handler; | |
| } | |
| return this.detectSupport().then( function ( isSupported ) { | |
| if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); | |
| if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { | |
| throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' ); | |
| } | |
| // Fall back to PNG or JPEG. | |
| return parser.loadTexture( textureIndex ); | |
| } ); | |
| } | |
| detectSupport() { | |
| if ( ! this.isSupported ) { | |
| this.isSupported = new Promise( function ( resolve ) { | |
| const image = new Image(); | |
| // Lossy test image. Support for lossy images doesn't guarantee support for all | |
| // WebP images, unfortunately. | |
| image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; | |
| image.onload = image.onerror = function () { | |
| resolve( image.height === 1 ); | |
| }; | |
| } ); | |
| } | |
| return this.isSupported; | |
| } | |
| } | |
| /** | |
| * AVIF Texture Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif | |
| */ | |
| class GLTFTextureAVIFExtension { | |
| constructor( parser ) { | |
| this.parser = parser; | |
| this.name = EXTENSIONS.EXT_TEXTURE_AVIF; | |
| this.isSupported = null; | |
| } | |
| loadTexture( textureIndex ) { | |
| const name = this.name; | |
| const parser = this.parser; | |
| const json = parser.json; | |
| const textureDef = json.textures[ textureIndex ]; | |
| if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { | |
| return null; | |
| } | |
| const extension = textureDef.extensions[ name ]; | |
| const source = json.images[ extension.source ]; | |
| let loader = parser.textureLoader; | |
| if ( source.uri ) { | |
| const handler = parser.options.manager.getHandler( source.uri ); | |
| if ( handler !== null ) loader = handler; | |
| } | |
| return this.detectSupport().then( function ( isSupported ) { | |
| if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); | |
| if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { | |
| throw new Error( 'THREE.GLTFLoader: AVIF required by asset but unsupported.' ); | |
| } | |
| // Fall back to PNG or JPEG. | |
| return parser.loadTexture( textureIndex ); | |
| } ); | |
| } | |
| detectSupport() { | |
| if ( ! this.isSupported ) { | |
| this.isSupported = new Promise( function ( resolve ) { | |
| const image = new Image(); | |
| // Lossy test image. | |
| image.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABcAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgABogQEDQgMgkQAAAAB8dSLfI='; | |
| image.onload = image.onerror = function () { | |
| resolve( image.height === 1 ); | |
| }; | |
| } ); | |
| } | |
| return this.isSupported; | |
| } | |
| } | |
| /** | |
| * meshopt BufferView Compression Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression | |
| */ | |
| class GLTFMeshoptCompression { | |
| constructor( parser ) { | |
| this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; | |
| this.parser = parser; | |
| } | |
| loadBufferView( index ) { | |
| const json = this.parser.json; | |
| const bufferView = json.bufferViews[ index ]; | |
| if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { | |
| const extensionDef = bufferView.extensions[ this.name ]; | |
| const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); | |
| const decoder = this.parser.options.meshoptDecoder; | |
| if ( ! decoder || ! decoder.supported ) { | |
| if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { | |
| throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' ); | |
| } else { | |
| // Assumes that the extension is optional and that fallback buffer data is present | |
| return null; | |
| } | |
| } | |
| return buffer.then( function ( res ) { | |
| const byteOffset = extensionDef.byteOffset || 0; | |
| const byteLength = extensionDef.byteLength || 0; | |
| const count = extensionDef.count; | |
| const stride = extensionDef.byteStride; | |
| const source = new Uint8Array( res, byteOffset, byteLength ); | |
| if ( decoder.decodeGltfBufferAsync ) { | |
| return decoder.decodeGltfBufferAsync( count, stride, source, extensionDef.mode, extensionDef.filter ).then( function ( res ) { | |
| return res.buffer; | |
| } ); | |
| } else { | |
| // Support for MeshoptDecoder 0.18 or earlier, without decodeGltfBufferAsync | |
| return decoder.ready.then( function () { | |
| const result = new ArrayBuffer( count * stride ); | |
| decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); | |
| return result; | |
| } ); | |
| } | |
| } ); | |
| } else { | |
| return null; | |
| } | |
| } | |
| } | |
| /** | |
| * GPU Instancing Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing | |
| * | |
| */ | |
| class GLTFMeshGpuInstancing { | |
| constructor( parser ) { | |
| this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING; | |
| this.parser = parser; | |
| } | |
| createNodeMesh( nodeIndex ) { | |
| const json = this.parser.json; | |
| const nodeDef = json.nodes[ nodeIndex ]; | |
| if ( ! nodeDef.extensions || ! nodeDef.extensions[ this.name ] || | |
| nodeDef.mesh === undefined ) { | |
| return null; | |
| } | |
| const meshDef = json.meshes[ nodeDef.mesh ]; | |
| // No Points or Lines + Instancing support yet | |
| for ( const primitive of meshDef.primitives ) { | |
| if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES && | |
| primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP && | |
| primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN && | |
| primitive.mode !== undefined ) { | |
| return null; | |
| } | |
| } | |
| const extensionDef = nodeDef.extensions[ this.name ]; | |
| const attributesDef = extensionDef.attributes; | |
| // @TODO: Can we support InstancedMesh + SkinnedMesh? | |
| const pending = []; | |
| const attributes = {}; | |
| for ( const key in attributesDef ) { | |
| pending.push( this.parser.getDependency( 'accessor', attributesDef[ key ] ).then( accessor => { | |
| attributes[ key ] = accessor; | |
| return attributes[ key ]; | |
| } ) ); | |
| } | |
| if ( pending.length < 1 ) { | |
| return null; | |
| } | |
| pending.push( this.parser.createNodeMesh( nodeIndex ) ); | |
| return Promise.all( pending ).then( results => { | |
| const nodeObject = results.pop(); | |
| const meshes = nodeObject.isGroup ? nodeObject.children : [ nodeObject ]; | |
| const count = results[ 0 ].count; // All attribute counts should be same | |
| const instancedMeshes = []; | |
| for ( const mesh of meshes ) { | |
| // Temporal variables | |
| const m = new Matrix4(); | |
| const p = new Vector3(); | |
| const q = new Quaternion(); | |
| const s = new Vector3( 1, 1, 1 ); | |
| const instancedMesh = new InstancedMesh( mesh.geometry, mesh.material, count ); | |
| for ( let i = 0; i < count; i ++ ) { | |
| if ( attributes.TRANSLATION ) { | |
| p.fromBufferAttribute( attributes.TRANSLATION, i ); | |
| } | |
| if ( attributes.ROTATION ) { | |
| q.fromBufferAttribute( attributes.ROTATION, i ); | |
| } | |
| if ( attributes.SCALE ) { | |
| s.fromBufferAttribute( attributes.SCALE, i ); | |
| } | |
| instancedMesh.setMatrixAt( i, m.compose( p, q, s ) ); | |
| } | |
| // Add instance attributes to the geometry, excluding TRS. | |
| for ( const attributeName in attributes ) { | |
| if ( attributeName === '_COLOR_0' ) { | |
| const attr = attributes[ attributeName ]; | |
| instancedMesh.instanceColor = new InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized ); | |
| } else if ( attributeName !== 'TRANSLATION' && | |
| attributeName !== 'ROTATION' && | |
| attributeName !== 'SCALE' ) { | |
| mesh.geometry.setAttribute( attributeName, attributes[ attributeName ] ); | |
| } | |
| } | |
| // Just in case | |
| Object3D.prototype.copy.call( instancedMesh, mesh ); | |
| this.parser.assignFinalMaterial( instancedMesh ); | |
| instancedMeshes.push( instancedMesh ); | |
| } | |
| if ( nodeObject.isGroup ) { | |
| nodeObject.clear(); | |
| nodeObject.add( ... instancedMeshes ); | |
| return nodeObject; | |
| } | |
| return instancedMeshes[ 0 ]; | |
| } ); | |
| } | |
| } | |
| /* BINARY EXTENSION */ | |
| const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; | |
| const BINARY_EXTENSION_HEADER_LENGTH = 12; | |
| const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; | |
| class GLTFBinaryExtension { | |
| constructor( data ) { | |
| this.name = EXTENSIONS.KHR_BINARY_GLTF; | |
| this.content = null; | |
| this.body = null; | |
| const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); | |
| const textDecoder = new TextDecoder(); | |
| this.header = { | |
| magic: textDecoder.decode( new Uint8Array( data.slice( 0, 4 ) ) ), | |
| version: headerView.getUint32( 4, true ), | |
| length: headerView.getUint32( 8, true ) | |
| }; | |
| if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { | |
| throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); | |
| } else if ( this.header.version < 2.0 ) { | |
| throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' ); | |
| } | |
| const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; | |
| const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); | |
| let chunkIndex = 0; | |
| while ( chunkIndex < chunkContentsLength ) { | |
| const chunkLength = chunkView.getUint32( chunkIndex, true ); | |
| chunkIndex += 4; | |
| const chunkType = chunkView.getUint32( chunkIndex, true ); | |
| chunkIndex += 4; | |
| if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { | |
| const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); | |
| this.content = textDecoder.decode( contentArray ); | |
| } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { | |
| const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; | |
| this.body = data.slice( byteOffset, byteOffset + chunkLength ); | |
| } | |
| // Clients must ignore chunks with unknown types. | |
| chunkIndex += chunkLength; | |
| } | |
| if ( this.content === null ) { | |
| throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); | |
| } | |
| } | |
| } | |
| /** | |
| * DRACO Mesh Compression Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression | |
| */ | |
| class GLTFDracoMeshCompressionExtension { | |
| constructor( json, dracoLoader ) { | |
| if ( ! dracoLoader ) { | |
| throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); | |
| } | |
| this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; | |
| this.json = json; | |
| this.dracoLoader = dracoLoader; | |
| this.dracoLoader.preload(); | |
| } | |
| decodePrimitive( primitive, parser ) { | |
| const json = this.json; | |
| const dracoLoader = this.dracoLoader; | |
| const bufferViewIndex = primitive.extensions[ this.name ].bufferView; | |
| const gltfAttributeMap = primitive.extensions[ this.name ].attributes; | |
| const threeAttributeMap = {}; | |
| const attributeNormalizedMap = {}; | |
| const attributeTypeMap = {}; | |
| for ( const attributeName in gltfAttributeMap ) { | |
| const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); | |
| threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; | |
| } | |
| for ( const attributeName in primitive.attributes ) { | |
| const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); | |
| if ( gltfAttributeMap[ attributeName ] !== undefined ) { | |
| const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; | |
| const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; | |
| attributeTypeMap[ threeAttributeName ] = componentType.name; | |
| attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; | |
| } | |
| } | |
| return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { | |
| return new Promise( function ( resolve ) { | |
| dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { | |
| for ( const attributeName in geometry.attributes ) { | |
| const attribute = geometry.attributes[ attributeName ]; | |
| const normalized = attributeNormalizedMap[ attributeName ]; | |
| if ( normalized !== undefined ) attribute.normalized = normalized; | |
| } | |
| resolve( geometry ); | |
| }, threeAttributeMap, attributeTypeMap ); | |
| } ); | |
| } ); | |
| } | |
| } | |
| /** | |
| * Texture Transform Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform | |
| */ | |
| class GLTFTextureTransformExtension { | |
| constructor() { | |
| this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; | |
| } | |
| extendTexture( texture, transform ) { | |
| if ( ( transform.texCoord === undefined || transform.texCoord === texture.channel ) | |
| && transform.offset === undefined | |
| && transform.rotation === undefined | |
| && transform.scale === undefined ) { | |
| // See https://github.com/mrdoob/three.js/issues/21819. | |
| return texture; | |
| } | |
| texture = texture.clone(); | |
| if ( transform.texCoord !== undefined ) { | |
| texture.channel = transform.texCoord; | |
| } | |
| if ( transform.offset !== undefined ) { | |
| texture.offset.fromArray( transform.offset ); | |
| } | |
| if ( transform.rotation !== undefined ) { | |
| texture.rotation = transform.rotation; | |
| } | |
| if ( transform.scale !== undefined ) { | |
| texture.repeat.fromArray( transform.scale ); | |
| } | |
| texture.needsUpdate = true; | |
| return texture; | |
| } | |
| } | |
| /** | |
| * Mesh Quantization Extension | |
| * | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization | |
| */ | |
| class GLTFMeshQuantizationExtension { | |
| constructor() { | |
| this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; | |
| } | |
| } | |
| /*********************************/ | |
| /********** INTERPOLATION ********/ | |
| /*********************************/ | |
| // Spline Interpolation | |
| // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation | |
| class GLTFCubicSplineInterpolant extends Interpolant { | |
| constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { | |
| super( parameterPositions, sampleValues, sampleSize, resultBuffer ); | |
| } | |
| copySampleValue_( index ) { | |
| // Copies a sample value to the result buffer. See description of glTF | |
| // CUBICSPLINE values layout in interpolate_() function below. | |
| const result = this.resultBuffer, | |
| values = this.sampleValues, | |
| valueSize = this.valueSize, | |
| offset = index * valueSize * 3 + valueSize; | |
| for ( let i = 0; i !== valueSize; i ++ ) { | |
| result[ i ] = values[ offset + i ]; | |
| } | |
| return result; | |
| } | |
| interpolate_( i1, t0, t, t1 ) { | |
| const result = this.resultBuffer; | |
| const values = this.sampleValues; | |
| const stride = this.valueSize; | |
| const stride2 = stride * 2; | |
| const stride3 = stride * 3; | |
| const td = t1 - t0; | |
| const p = ( t - t0 ) / td; | |
| const pp = p * p; | |
| const ppp = pp * p; | |
| const offset1 = i1 * stride3; | |
| const offset0 = offset1 - stride3; | |
| const s2 = - 2 * ppp + 3 * pp; | |
| const s3 = ppp - pp; | |
| const s0 = 1 - s2; | |
| const s1 = s3 - pp + p; | |
| // Layout of keyframe output values for CUBICSPLINE animations: | |
| // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] | |
| for ( let i = 0; i !== stride; i ++ ) { | |
| const p0 = values[ offset0 + i + stride ]; // splineVertex_k | |
| const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) | |
| const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 | |
| const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) | |
| result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; | |
| } | |
| return result; | |
| } | |
| } | |
| const _q = new Quaternion(); | |
| class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { | |
| interpolate_( i1, t0, t, t1 ) { | |
| const result = super.interpolate_( i1, t0, t, t1 ); | |
| _q.fromArray( result ).normalize().toArray( result ); | |
| return result; | |
| } | |
| } | |
| /*********************************/ | |
| /********** INTERNALS ************/ | |
| /*********************************/ | |
| /* CONSTANTS */ | |
| const WEBGL_CONSTANTS = { | |
| FLOAT: 5126, | |
| //FLOAT_MAT2: 35674, | |
| FLOAT_MAT3: 35675, | |
| FLOAT_MAT4: 35676, | |
| FLOAT_VEC2: 35664, | |
| FLOAT_VEC3: 35665, | |
| FLOAT_VEC4: 35666, | |
| LINEAR: 9729, | |
| REPEAT: 10497, | |
| SAMPLER_2D: 35678, | |
| POINTS: 0, | |
| LINES: 1, | |
| LINE_LOOP: 2, | |
| LINE_STRIP: 3, | |
| TRIANGLES: 4, | |
| TRIANGLE_STRIP: 5, | |
| TRIANGLE_FAN: 6, | |
| UNSIGNED_BYTE: 5121, | |
| UNSIGNED_SHORT: 5123 | |
| }; | |
| const WEBGL_COMPONENT_TYPES = { | |
| 5120: Int8Array, | |
| 5121: Uint8Array, | |
| 5122: Int16Array, | |
| 5123: Uint16Array, | |
| 5125: Uint32Array, | |
| 5126: Float32Array | |
| }; | |
| const WEBGL_FILTERS = { | |
| 9728: NearestFilter, | |
| 9729: LinearFilter, | |
| 9984: NearestMipmapNearestFilter, | |
| 9985: LinearMipmapNearestFilter, | |
| 9986: NearestMipmapLinearFilter, | |
| 9987: LinearMipmapLinearFilter | |
| }; | |
| const WEBGL_WRAPPINGS = { | |
| 33071: ClampToEdgeWrapping, | |
| 33648: MirroredRepeatWrapping, | |
| 10497: RepeatWrapping | |
| }; | |
| const WEBGL_TYPE_SIZES = { | |
| 'SCALAR': 1, | |
| 'VEC2': 2, | |
| 'VEC3': 3, | |
| 'VEC4': 4, | |
| 'MAT2': 4, | |
| 'MAT3': 9, | |
| 'MAT4': 16 | |
| }; | |
| const ATTRIBUTES = { | |
| POSITION: 'position', | |
| NORMAL: 'normal', | |
| TANGENT: 'tangent', | |
| TEXCOORD_0: 'uv', | |
| TEXCOORD_1: 'uv1', | |
| TEXCOORD_2: 'uv2', | |
| TEXCOORD_3: 'uv3', | |
| COLOR_0: 'color', | |
| WEIGHTS_0: 'skinWeight', | |
| JOINTS_0: 'skinIndex', | |
| }; | |
| const PATH_PROPERTIES = { | |
| scale: 'scale', | |
| translation: 'position', | |
| rotation: 'quaternion', | |
| weights: 'morphTargetInfluences' | |
| }; | |
| const INTERPOLATION = { | |
| CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each | |
| // keyframe track will be initialized with a default interpolation type, then modified. | |
| LINEAR: InterpolateLinear, | |
| STEP: InterpolateDiscrete | |
| }; | |
| const ALPHA_MODES = { | |
| OPAQUE: 'OPAQUE', | |
| MASK: 'MASK', | |
| BLEND: 'BLEND' | |
| }; | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material | |
| */ | |
| function createDefaultMaterial( cache ) { | |
| if ( cache[ 'DefaultMaterial' ] === undefined ) { | |
| cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( { | |
| color: 0xFFFFFF, | |
| emissive: 0x000000, | |
| metalness: 1, | |
| roughness: 1, | |
| transparent: false, | |
| depthTest: true, | |
| side: FrontSide | |
| } ); | |
| } | |
| return cache[ 'DefaultMaterial' ]; | |
| } | |
| function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { | |
| // Add unknown glTF extensions to an object's userData. | |
| for ( const name in objectDef.extensions ) { | |
| if ( knownExtensions[ name ] === undefined ) { | |
| object.userData.gltfExtensions = object.userData.gltfExtensions || {}; | |
| object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; | |
| } | |
| } | |
| } | |
| /** | |
| * @param {Object3D|Material|BufferGeometry} object | |
| * @param {GLTF.definition} gltfDef | |
| */ | |
| function assignExtrasToUserData( object, gltfDef ) { | |
| if ( gltfDef.extras !== undefined ) { | |
| if ( typeof gltfDef.extras === 'object' ) { | |
| Object.assign( object.userData, gltfDef.extras ); | |
| } else { | |
| console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); | |
| } | |
| } | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets | |
| * | |
| * @param {BufferGeometry} geometry | |
| * @param {Array<GLTF.Target>} targets | |
| * @param {GLTFParser} parser | |
| * @return {Promise<BufferGeometry>} | |
| */ | |
| function addMorphTargets( geometry, targets, parser ) { | |
| let hasMorphPosition = false; | |
| let hasMorphNormal = false; | |
| let hasMorphColor = false; | |
| for ( let i = 0, il = targets.length; i < il; i ++ ) { | |
| const target = targets[ i ]; | |
| if ( target.POSITION !== undefined ) hasMorphPosition = true; | |
| if ( target.NORMAL !== undefined ) hasMorphNormal = true; | |
| if ( target.COLOR_0 !== undefined ) hasMorphColor = true; | |
| if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break; | |
| } | |
| if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry ); | |
| const pendingPositionAccessors = []; | |
| const pendingNormalAccessors = []; | |
| const pendingColorAccessors = []; | |
| for ( let i = 0, il = targets.length; i < il; i ++ ) { | |
| const target = targets[ i ]; | |
| if ( hasMorphPosition ) { | |
| const pendingAccessor = target.POSITION !== undefined | |
| ? parser.getDependency( 'accessor', target.POSITION ) | |
| : geometry.attributes.position; | |
| pendingPositionAccessors.push( pendingAccessor ); | |
| } | |
| if ( hasMorphNormal ) { | |
| const pendingAccessor = target.NORMAL !== undefined | |
| ? parser.getDependency( 'accessor', target.NORMAL ) | |
| : geometry.attributes.normal; | |
| pendingNormalAccessors.push( pendingAccessor ); | |
| } | |
| if ( hasMorphColor ) { | |
| const pendingAccessor = target.COLOR_0 !== undefined | |
| ? parser.getDependency( 'accessor', target.COLOR_0 ) | |
| : geometry.attributes.color; | |
| pendingColorAccessors.push( pendingAccessor ); | |
| } | |
| } | |
| return Promise.all( [ | |
| Promise.all( pendingPositionAccessors ), | |
| Promise.all( pendingNormalAccessors ), | |
| Promise.all( pendingColorAccessors ) | |
| ] ).then( function ( accessors ) { | |
| const morphPositions = accessors[ 0 ]; | |
| const morphNormals = accessors[ 1 ]; | |
| const morphColors = accessors[ 2 ]; | |
| if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; | |
| if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; | |
| if ( hasMorphColor ) geometry.morphAttributes.color = morphColors; | |
| geometry.morphTargetsRelative = true; | |
| return geometry; | |
| } ); | |
| } | |
| /** | |
| * @param {Mesh} mesh | |
| * @param {GLTF.Mesh} meshDef | |
| */ | |
| function updateMorphTargets( mesh, meshDef ) { | |
| mesh.updateMorphTargets(); | |
| if ( meshDef.weights !== undefined ) { | |
| for ( let i = 0, il = meshDef.weights.length; i < il; i ++ ) { | |
| mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; | |
| } | |
| } | |
| // .extras has user-defined data, so check that .extras.targetNames is an array. | |
| if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { | |
| const targetNames = meshDef.extras.targetNames; | |
| if ( mesh.morphTargetInfluences.length === targetNames.length ) { | |
| mesh.morphTargetDictionary = {}; | |
| for ( let i = 0, il = targetNames.length; i < il; i ++ ) { | |
| mesh.morphTargetDictionary[ targetNames[ i ] ] = i; | |
| } | |
| } else { | |
| console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); | |
| } | |
| } | |
| } | |
| function createPrimitiveKey( primitiveDef ) { | |
| let geometryKey; | |
| const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; | |
| if ( dracoExtension ) { | |
| geometryKey = 'draco:' + dracoExtension.bufferView | |
| + ':' + dracoExtension.indices | |
| + ':' + createAttributesKey( dracoExtension.attributes ); | |
| } else { | |
| geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; | |
| } | |
| if ( primitiveDef.targets !== undefined ) { | |
| for ( let i = 0, il = primitiveDef.targets.length; i < il; i ++ ) { | |
| geometryKey += ':' + createAttributesKey( primitiveDef.targets[ i ] ); | |
| } | |
| } | |
| return geometryKey; | |
| } | |
| function createAttributesKey( attributes ) { | |
| let attributesKey = ''; | |
| const keys = Object.keys( attributes ).sort(); | |
| for ( let i = 0, il = keys.length; i < il; i ++ ) { | |
| attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; | |
| } | |
| return attributesKey; | |
| } | |
| function getNormalizedComponentScale( constructor ) { | |
| // Reference: | |
| // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data | |
| switch ( constructor ) { | |
| case Int8Array: | |
| return 1 / 127; | |
| case Uint8Array: | |
| return 1 / 255; | |
| case Int16Array: | |
| return 1 / 32767; | |
| case Uint16Array: | |
| return 1 / 65535; | |
| default: | |
| throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); | |
| } | |
| } | |
| function getImageURIMimeType( uri ) { | |
| if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; | |
| if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; | |
| return 'image/png'; | |
| } | |
| const _identityMatrix = new Matrix4(); | |
| /* GLTF PARSER */ | |
| class GLTFParser { | |
| constructor( json = {}, options = {} ) { | |
| this.json = json; | |
| this.extensions = {}; | |
| this.plugins = {}; | |
| this.options = options; | |
| // loader object cache | |
| this.cache = new GLTFRegistry(); | |
| // associations between Three.js objects and glTF elements | |
| this.associations = new Map(); | |
| // BufferGeometry caching | |
| this.primitiveCache = {}; | |
| // Node cache | |
| this.nodeCache = {}; | |
| // Object3D instance caches | |
| this.meshCache = { refs: {}, uses: {} }; | |
| this.cameraCache = { refs: {}, uses: {} }; | |
| this.lightCache = { refs: {}, uses: {} }; | |
| this.sourceCache = {}; | |
| this.textureCache = {}; | |
| // Track node names, to ensure no duplicates | |
| this.nodeNamesUsed = {}; | |
| // Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the | |
| // expensive work of uploading a texture to the GPU off the main thread. | |
| let isSafari = false; | |
| let isFirefox = false; | |
| let firefoxVersion = - 1; | |
| if ( typeof navigator !== 'undefined' ) { | |
| isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === true; | |
| isFirefox = navigator.userAgent.indexOf( 'Firefox' ) > - 1; | |
| firefoxVersion = isFirefox ? navigator.userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1; | |
| } | |
| if ( typeof createImageBitmap === 'undefined' || isSafari || ( isFirefox && firefoxVersion < 98 ) ) { | |
| this.textureLoader = new TextureLoader( this.options.manager ); | |
| } else { | |
| this.textureLoader = new ImageBitmapLoader( this.options.manager ); | |
| } | |
| this.textureLoader.setCrossOrigin( this.options.crossOrigin ); | |
| this.textureLoader.setRequestHeader( this.options.requestHeader ); | |
| this.fileLoader = new FileLoader( this.options.manager ); | |
| this.fileLoader.setResponseType( 'arraybuffer' ); | |
| if ( this.options.crossOrigin === 'use-credentials' ) { | |
| this.fileLoader.setWithCredentials( true ); | |
| } | |
| } | |
| setExtensions( extensions ) { | |
| this.extensions = extensions; | |
| } | |
| setPlugins( plugins ) { | |
| this.plugins = plugins; | |
| } | |
| parse( onLoad, onError ) { | |
| const parser = this; | |
| const json = this.json; | |
| const extensions = this.extensions; | |
| // Clear the loader cache | |
| this.cache.removeAll(); | |
| this.nodeCache = {}; | |
| // Mark the special nodes/meshes in json for efficient parse | |
| this._invokeAll( function ( ext ) { | |
| return ext._markDefs && ext._markDefs(); | |
| } ); | |
| Promise.all( this._invokeAll( function ( ext ) { | |
| return ext.beforeRoot && ext.beforeRoot(); | |
| } ) ).then( function () { | |
| return Promise.all( [ | |
| parser.getDependencies( 'scene' ), | |
| parser.getDependencies( 'animation' ), | |
| parser.getDependencies( 'camera' ), | |
| ] ); | |
| } ).then( function ( dependencies ) { | |
| const result = { | |
| scene: dependencies[ 0 ][ json.scene || 0 ], | |
| scenes: dependencies[ 0 ], | |
| animations: dependencies[ 1 ], | |
| cameras: dependencies[ 2 ], | |
| asset: json.asset, | |
| parser: parser, | |
| userData: {} | |
| }; | |
| addUnknownExtensionsToUserData( extensions, result, json ); | |
| assignExtrasToUserData( result, json ); | |
| return Promise.all( parser._invokeAll( function ( ext ) { | |
| return ext.afterRoot && ext.afterRoot( result ); | |
| } ) ).then( function () { | |
| onLoad( result ); | |
| } ); | |
| } ).catch( onError ); | |
| } | |
| /** | |
| * Marks the special nodes/meshes in json for efficient parse. | |
| */ | |
| _markDefs() { | |
| const nodeDefs = this.json.nodes || []; | |
| const skinDefs = this.json.skins || []; | |
| const meshDefs = this.json.meshes || []; | |
| // Nothing in the node definition indicates whether it is a Bone or an | |
| // Object3D. Use the skins' joint references to mark bones. | |
| for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { | |
| const joints = skinDefs[ skinIndex ].joints; | |
| for ( let i = 0, il = joints.length; i < il; i ++ ) { | |
| nodeDefs[ joints[ i ] ].isBone = true; | |
| } | |
| } | |
| // Iterate over all nodes, marking references to shared resources, | |
| // as well as skeleton joints. | |
| for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { | |
| const nodeDef = nodeDefs[ nodeIndex ]; | |
| if ( nodeDef.mesh !== undefined ) { | |
| this._addNodeRef( this.meshCache, nodeDef.mesh ); | |
| // Nothing in the mesh definition indicates whether it is | |
| // a SkinnedMesh or Mesh. Use the node's mesh reference | |
| // to mark SkinnedMesh if node has skin. | |
| if ( nodeDef.skin !== undefined ) { | |
| meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; | |
| } | |
| } | |
| if ( nodeDef.camera !== undefined ) { | |
| this._addNodeRef( this.cameraCache, nodeDef.camera ); | |
| } | |
| } | |
| } | |
| /** | |
| * Counts references to shared node / Object3D resources. These resources | |
| * can be reused, or "instantiated", at multiple nodes in the scene | |
| * hierarchy. Mesh, Camera, and Light instances are instantiated and must | |
| * be marked. Non-scenegraph resources (like Materials, Geometries, and | |
| * Textures) can be reused directly and are not marked here. | |
| * | |
| * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. | |
| */ | |
| _addNodeRef( cache, index ) { | |
| if ( index === undefined ) return; | |
| if ( cache.refs[ index ] === undefined ) { | |
| cache.refs[ index ] = cache.uses[ index ] = 0; | |
| } | |
| cache.refs[ index ] ++; | |
| } | |
| /** Returns a reference to a shared resource, cloning it if necessary. */ | |
| _getNodeRef( cache, index, object ) { | |
| if ( cache.refs[ index ] <= 1 ) return object; | |
| const ref = object.clone(); | |
| // Propagates mappings to the cloned object, prevents mappings on the | |
| // original object from being lost. | |
| const updateMappings = ( original, clone ) => { | |
| const mappings = this.associations.get( original ); | |
| if ( mappings != null ) { | |
| this.associations.set( clone, mappings ); | |
| } | |
| for ( const [ i, child ] of original.children.entries() ) { | |
| updateMappings( child, clone.children[ i ] ); | |
| } | |
| }; | |
| updateMappings( object, ref ); | |
| ref.name += '_instance_' + ( cache.uses[ index ] ++ ); | |
| return ref; | |
| } | |
| _invokeOne( func ) { | |
| const extensions = Object.values( this.plugins ); | |
| extensions.push( this ); | |
| for ( let i = 0; i < extensions.length; i ++ ) { | |
| const result = func( extensions[ i ] ); | |
| if ( result ) return result; | |
| } | |
| return null; | |
| } | |
| _invokeAll( func ) { | |
| const extensions = Object.values( this.plugins ); | |
| extensions.unshift( this ); | |
| const pending = []; | |
| for ( let i = 0; i < extensions.length; i ++ ) { | |
| const result = func( extensions[ i ] ); | |
| if ( result ) pending.push( result ); | |
| } | |
| return pending; | |
| } | |
| /** | |
| * Requests the specified dependency asynchronously, with caching. | |
| * @param {string} type | |
| * @param {number} index | |
| * @return {Promise<Object3D|Material|THREE.Texture|AnimationClip|ArrayBuffer|Object>} | |
| */ | |
| getDependency( type, index ) { | |
| const cacheKey = type + ':' + index; | |
| let dependency = this.cache.get( cacheKey ); | |
| if ( ! dependency ) { | |
| switch ( type ) { | |
| case 'scene': | |
| dependency = this.loadScene( index ); | |
| break; | |
| case 'node': | |
| dependency = this._invokeOne( function ( ext ) { | |
| return ext.loadNode && ext.loadNode( index ); | |
| } ); | |
| break; | |
| case 'mesh': | |
| dependency = this._invokeOne( function ( ext ) { | |
| return ext.loadMesh && ext.loadMesh( index ); | |
| } ); | |
| break; | |
| case 'accessor': | |
| dependency = this.loadAccessor( index ); | |
| break; | |
| case 'bufferView': | |
| dependency = this._invokeOne( function ( ext ) { | |
| return ext.loadBufferView && ext.loadBufferView( index ); | |
| } ); | |
| break; | |
| case 'buffer': | |
| dependency = this.loadBuffer( index ); | |
| break; | |
| case 'material': | |
| dependency = this._invokeOne( function ( ext ) { | |
| return ext.loadMaterial && ext.loadMaterial( index ); | |
| } ); | |
| break; | |
| case 'texture': | |
| dependency = this._invokeOne( function ( ext ) { | |
| return ext.loadTexture && ext.loadTexture( index ); | |
| } ); | |
| break; | |
| case 'skin': | |
| dependency = this.loadSkin( index ); | |
| break; | |
| case 'animation': | |
| dependency = this._invokeOne( function ( ext ) { | |
| return ext.loadAnimation && ext.loadAnimation( index ); | |
| } ); | |
| break; | |
| case 'camera': | |
| dependency = this.loadCamera( index ); | |
| break; | |
| default: | |
| dependency = this._invokeOne( function ( ext ) { | |
| return ext != this && ext.getDependency && ext.getDependency( type, index ); | |
| } ); | |
| if ( ! dependency ) { | |
| throw new Error( 'Unknown type: ' + type ); | |
| } | |
| break; | |
| } | |
| this.cache.add( cacheKey, dependency ); | |
| } | |
| return dependency; | |
| } | |
| /** | |
| * Requests all dependencies of the specified type asynchronously, with caching. | |
| * @param {string} type | |
| * @return {Promise<Array<Object>>} | |
| */ | |
| getDependencies( type ) { | |
| let dependencies = this.cache.get( type ); | |
| if ( ! dependencies ) { | |
| const parser = this; | |
| const defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; | |
| dependencies = Promise.all( defs.map( function ( def, index ) { | |
| return parser.getDependency( type, index ); | |
| } ) ); | |
| this.cache.add( type, dependencies ); | |
| } | |
| return dependencies; | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views | |
| * @param {number} bufferIndex | |
| * @return {Promise<ArrayBuffer>} | |
| */ | |
| loadBuffer( bufferIndex ) { | |
| const bufferDef = this.json.buffers[ bufferIndex ]; | |
| const loader = this.fileLoader; | |
| if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { | |
| throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); | |
| } | |
| // If present, GLB container is required to be the first buffer. | |
| if ( bufferDef.uri === undefined && bufferIndex === 0 ) { | |
| return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); | |
| } | |
| const options = this.options; | |
| return new Promise( function ( resolve, reject ) { | |
| loader.load( LoaderUtils.resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { | |
| reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); | |
| } ); | |
| } ); | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views | |
| * @param {number} bufferViewIndex | |
| * @return {Promise<ArrayBuffer>} | |
| */ | |
| loadBufferView( bufferViewIndex ) { | |
| const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; | |
| return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { | |
| const byteLength = bufferViewDef.byteLength || 0; | |
| const byteOffset = bufferViewDef.byteOffset || 0; | |
| return buffer.slice( byteOffset, byteOffset + byteLength ); | |
| } ); | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors | |
| * @param {number} accessorIndex | |
| * @return {Promise<BufferAttribute|InterleavedBufferAttribute>} | |
| */ | |
| loadAccessor( accessorIndex ) { | |
| const parser = this; | |
| const json = this.json; | |
| const accessorDef = this.json.accessors[ accessorIndex ]; | |
| if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { | |
| const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; | |
| const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; | |
| const normalized = accessorDef.normalized === true; | |
| const array = new TypedArray( accessorDef.count * itemSize ); | |
| return Promise.resolve( new BufferAttribute( array, itemSize, normalized ) ); | |
| } | |
| const pendingBufferViews = []; | |
| if ( accessorDef.bufferView !== undefined ) { | |
| pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); | |
| } else { | |
| pendingBufferViews.push( null ); | |
| } | |
| if ( accessorDef.sparse !== undefined ) { | |
| pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); | |
| pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); | |
| } | |
| return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { | |
| const bufferView = bufferViews[ 0 ]; | |
| const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; | |
| const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; | |
| // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. | |
| const elementBytes = TypedArray.BYTES_PER_ELEMENT; | |
| const itemBytes = elementBytes * itemSize; | |
| const byteOffset = accessorDef.byteOffset || 0; | |
| const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; | |
| const normalized = accessorDef.normalized === true; | |
| let array, bufferAttribute; | |
| // The buffer is not interleaved if the stride is the item size in bytes. | |
| if ( byteStride && byteStride !== itemBytes ) { | |
| // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer | |
| // This makes sure that IBA.count reflects accessor.count properly | |
| const ibSlice = Math.floor( byteOffset / byteStride ); | |
| const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; | |
| let ib = parser.cache.get( ibCacheKey ); | |
| if ( ! ib ) { | |
| array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes ); | |
| // Integer parameters to IB/IBA are in array elements, not bytes. | |
| ib = new InterleavedBuffer( array, byteStride / elementBytes ); | |
| parser.cache.add( ibCacheKey, ib ); | |
| } | |
| bufferAttribute = new InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized ); | |
| } else { | |
| if ( bufferView === null ) { | |
| array = new TypedArray( accessorDef.count * itemSize ); | |
| } else { | |
| array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); | |
| } | |
| bufferAttribute = new BufferAttribute( array, itemSize, normalized ); | |
| } | |
| // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors | |
| if ( accessorDef.sparse !== undefined ) { | |
| const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; | |
| const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; | |
| const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; | |
| const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; | |
| const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); | |
| const sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); | |
| if ( bufferView !== null ) { | |
| // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. | |
| bufferAttribute = new BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized ); | |
| } | |
| for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { | |
| const index = sparseIndices[ i ]; | |
| bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); | |
| if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); | |
| if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); | |
| if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); | |
| if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); | |
| } | |
| } | |
| return bufferAttribute; | |
| } ); | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures | |
| * @param {number} textureIndex | |
| * @return {Promise<THREE.Texture|null>} | |
| */ | |
| loadTexture( textureIndex ) { | |
| const json = this.json; | |
| const options = this.options; | |
| const textureDef = json.textures[ textureIndex ]; | |
| const sourceIndex = textureDef.source; | |
| const sourceDef = json.images[ sourceIndex ]; | |
| let loader = this.textureLoader; | |
| if ( sourceDef.uri ) { | |
| const handler = options.manager.getHandler( sourceDef.uri ); | |
| if ( handler !== null ) loader = handler; | |
| } | |
| return this.loadTextureImage( textureIndex, sourceIndex, loader ); | |
| } | |
| loadTextureImage( textureIndex, sourceIndex, loader ) { | |
| const parser = this; | |
| const json = this.json; | |
| const textureDef = json.textures[ textureIndex ]; | |
| const sourceDef = json.images[ sourceIndex ]; | |
| const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler; | |
| if ( this.textureCache[ cacheKey ] ) { | |
| // See https://github.com/mrdoob/three.js/issues/21559. | |
| return this.textureCache[ cacheKey ]; | |
| } | |
| const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) { | |
| texture.flipY = false; | |
| texture.name = textureDef.name || sourceDef.name || ''; | |
| if ( texture.name === '' && typeof sourceDef.uri === 'string' && sourceDef.uri.startsWith( 'data:image/' ) === false ) { | |
| texture.name = sourceDef.uri; | |
| } | |
| const samplers = json.samplers || {}; | |
| const sampler = samplers[ textureDef.sampler ] || {}; | |
| texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter; | |
| texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; | |
| texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping; | |
| texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping; | |
| parser.associations.set( texture, { textures: textureIndex } ); | |
| return texture; | |
| } ).catch( function () { | |
| return null; | |
| } ); | |
| this.textureCache[ cacheKey ] = promise; | |
| return promise; | |
| } | |
| loadImageSource( sourceIndex, loader ) { | |
| const parser = this; | |
| const json = this.json; | |
| const options = this.options; | |
| if ( this.sourceCache[ sourceIndex ] !== undefined ) { | |
| return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() ); | |
| } | |
| const sourceDef = json.images[ sourceIndex ]; | |
| const URL = self.URL || self.webkitURL; | |
| let sourceURI = sourceDef.uri || ''; | |
| let isObjectURL = false; | |
| if ( sourceDef.bufferView !== undefined ) { | |
| // Load binary image data from bufferView, if provided. | |
| sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) { | |
| isObjectURL = true; | |
| const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } ); | |
| sourceURI = URL.createObjectURL( blob ); | |
| return sourceURI; | |
| } ); | |
| } else if ( sourceDef.uri === undefined ) { | |
| throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' ); | |
| } | |
| const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) { | |
| return new Promise( function ( resolve, reject ) { | |
| let onLoad = resolve; | |
| if ( loader.isImageBitmapLoader === true ) { | |
| onLoad = function ( imageBitmap ) { | |
| const texture = new Texture( imageBitmap ); | |
| texture.needsUpdate = true; | |
| resolve( texture ); | |
| }; | |
| } | |
| loader.load( LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject ); | |
| } ); | |
| } ).then( function ( texture ) { | |
| // Clean up resources and configure Texture. | |
| if ( isObjectURL === true ) { | |
| URL.revokeObjectURL( sourceURI ); | |
| } | |
| texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); | |
| return texture; | |
| } ).catch( function ( error ) { | |
| console.error( 'THREE.GLTFLoader: Couldn\'t load texture', sourceURI ); | |
| throw error; | |
| } ); | |
| this.sourceCache[ sourceIndex ] = promise; | |
| return promise; | |
| } | |
| /** | |
| * Asynchronously assigns a texture to the given material parameters. | |
| * @param {Object} materialParams | |
| * @param {string} mapName | |
| * @param {Object} mapDef | |
| * @return {Promise<Texture>} | |
| */ | |
| assignTexture( materialParams, mapName, mapDef, colorSpace ) { | |
| const parser = this; | |
| return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { | |
| if ( ! texture ) return null; | |
| if ( mapDef.texCoord !== undefined && mapDef.texCoord > 0 ) { | |
| texture = texture.clone(); | |
| texture.channel = mapDef.texCoord; | |
| } | |
| if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { | |
| const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; | |
| if ( transform ) { | |
| const gltfReference = parser.associations.get( texture ); | |
| texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); | |
| parser.associations.set( texture, gltfReference ); | |
| } | |
| } | |
| if ( colorSpace !== undefined ) { | |
| texture.colorSpace = colorSpace; | |
| } | |
| materialParams[ mapName ] = texture; | |
| return texture; | |
| } ); | |
| } | |
| /** | |
| * Assigns final material to a Mesh, Line, or Points instance. The instance | |
| * already has a material (generated from the glTF material options alone) | |
| * but reuse of the same glTF material may require multiple threejs materials | |
| * to accommodate different primitive types, defines, etc. New materials will | |
| * be created if necessary, and reused from a cache. | |
| * @param {Object3D} mesh Mesh, Line, or Points instance. | |
| */ | |
| assignFinalMaterial( mesh ) { | |
| const geometry = mesh.geometry; | |
| let material = mesh.material; | |
| const useDerivativeTangents = geometry.attributes.tangent === undefined; | |
| const useVertexColors = geometry.attributes.color !== undefined; | |
| const useFlatShading = geometry.attributes.normal === undefined; | |
| if ( mesh.isPoints ) { | |
| const cacheKey = 'PointsMaterial:' + material.uuid; | |
| let pointsMaterial = this.cache.get( cacheKey ); | |
| if ( ! pointsMaterial ) { | |
| pointsMaterial = new PointsMaterial(); | |
| Material.prototype.copy.call( pointsMaterial, material ); | |
| pointsMaterial.color.copy( material.color ); | |
| pointsMaterial.map = material.map; | |
| pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px | |
| this.cache.add( cacheKey, pointsMaterial ); | |
| } | |
| material = pointsMaterial; | |
| } else if ( mesh.isLine ) { | |
| const cacheKey = 'LineBasicMaterial:' + material.uuid; | |
| let lineMaterial = this.cache.get( cacheKey ); | |
| if ( ! lineMaterial ) { | |
| lineMaterial = new LineBasicMaterial(); | |
| Material.prototype.copy.call( lineMaterial, material ); | |
| lineMaterial.color.copy( material.color ); | |
| lineMaterial.map = material.map; | |
| this.cache.add( cacheKey, lineMaterial ); | |
| } | |
| material = lineMaterial; | |
| } | |
| // Clone the material if it will be modified | |
| if ( useDerivativeTangents || useVertexColors || useFlatShading ) { | |
| let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; | |
| if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; | |
| if ( useVertexColors ) cacheKey += 'vertex-colors:'; | |
| if ( useFlatShading ) cacheKey += 'flat-shading:'; | |
| let cachedMaterial = this.cache.get( cacheKey ); | |
| if ( ! cachedMaterial ) { | |
| cachedMaterial = material.clone(); | |
| if ( useVertexColors ) cachedMaterial.vertexColors = true; | |
| if ( useFlatShading ) cachedMaterial.flatShading = true; | |
| if ( useDerivativeTangents ) { | |
| // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 | |
| if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; | |
| if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1; | |
| } | |
| this.cache.add( cacheKey, cachedMaterial ); | |
| this.associations.set( cachedMaterial, this.associations.get( material ) ); | |
| } | |
| material = cachedMaterial; | |
| } | |
| mesh.material = material; | |
| } | |
| getMaterialType( /* materialIndex */ ) { | |
| return MeshStandardMaterial; | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials | |
| * @param {number} materialIndex | |
| * @return {Promise<Material>} | |
| */ | |
| loadMaterial( materialIndex ) { | |
| const parser = this; | |
| const json = this.json; | |
| const extensions = this.extensions; | |
| const materialDef = json.materials[ materialIndex ]; | |
| let materialType; | |
| const materialParams = {}; | |
| const materialExtensions = materialDef.extensions || {}; | |
| const pending = []; | |
| if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { | |
| const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; | |
| materialType = kmuExtension.getMaterialType(); | |
| pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); | |
| } else { | |
| // Specification: | |
| // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material | |
| const metallicRoughness = materialDef.pbrMetallicRoughness || {}; | |
| materialParams.color = new Color( 1.0, 1.0, 1.0 ); | |
| materialParams.opacity = 1.0; | |
| if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { | |
| const array = metallicRoughness.baseColorFactor; | |
| materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace ); | |
| materialParams.opacity = array[ 3 ]; | |
| } | |
| if ( metallicRoughness.baseColorTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, SRGBColorSpace ) ); | |
| } | |
| materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; | |
| materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; | |
| if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { | |
| pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) ); | |
| pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) ); | |
| } | |
| materialType = this._invokeOne( function ( ext ) { | |
| return ext.getMaterialType && ext.getMaterialType( materialIndex ); | |
| } ); | |
| pending.push( Promise.all( this._invokeAll( function ( ext ) { | |
| return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams ); | |
| } ) ) ); | |
| } | |
| if ( materialDef.doubleSided === true ) { | |
| materialParams.side = DoubleSide; | |
| } | |
| const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; | |
| if ( alphaMode === ALPHA_MODES.BLEND ) { | |
| materialParams.transparent = true; | |
| // See: https://github.com/mrdoob/three.js/issues/17706 | |
| materialParams.depthWrite = false; | |
| } else { | |
| materialParams.transparent = false; | |
| if ( alphaMode === ALPHA_MODES.MASK ) { | |
| materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; | |
| } | |
| } | |
| if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) { | |
| pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); | |
| materialParams.normalScale = new Vector2( 1, 1 ); | |
| if ( materialDef.normalTexture.scale !== undefined ) { | |
| const scale = materialDef.normalTexture.scale; | |
| materialParams.normalScale.set( scale, scale ); | |
| } | |
| } | |
| if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) { | |
| pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); | |
| if ( materialDef.occlusionTexture.strength !== undefined ) { | |
| materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; | |
| } | |
| } | |
| if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) { | |
| const emissiveFactor = materialDef.emissiveFactor; | |
| materialParams.emissive = new Color().setRGB( emissiveFactor[ 0 ], emissiveFactor[ 1 ], emissiveFactor[ 2 ], LinearSRGBColorSpace ); | |
| } | |
| if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) { | |
| pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, SRGBColorSpace ) ); | |
| } | |
| return Promise.all( pending ).then( function () { | |
| const material = new materialType( materialParams ); | |
| if ( materialDef.name ) material.name = materialDef.name; | |
| assignExtrasToUserData( material, materialDef ); | |
| parser.associations.set( material, { materials: materialIndex } ); | |
| if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); | |
| return material; | |
| } ); | |
| } | |
| /** When Object3D instances are targeted by animation, they need unique names. */ | |
| createUniqueName( originalName ) { | |
| const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); | |
| if ( sanitizedName in this.nodeNamesUsed ) { | |
| return sanitizedName + '_' + ( ++ this.nodeNamesUsed[ sanitizedName ] ); | |
| } else { | |
| this.nodeNamesUsed[ sanitizedName ] = 0; | |
| return sanitizedName; | |
| } | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry | |
| * | |
| * Creates BufferGeometries from primitives. | |
| * | |
| * @param {Array<GLTF.Primitive>} primitives | |
| * @return {Promise<Array<BufferGeometry>>} | |
| */ | |
| loadGeometries( primitives ) { | |
| const parser = this; | |
| const extensions = this.extensions; | |
| const cache = this.primitiveCache; | |
| function createDracoPrimitive( primitive ) { | |
| return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] | |
| .decodePrimitive( primitive, parser ) | |
| .then( function ( geometry ) { | |
| return addPrimitiveAttributes( geometry, primitive, parser ); | |
| } ); | |
| } | |
| const pending = []; | |
| for ( let i = 0, il = primitives.length; i < il; i ++ ) { | |
| const primitive = primitives[ i ]; | |
| const cacheKey = createPrimitiveKey( primitive ); | |
| // See if we've already created this geometry | |
| const cached = cache[ cacheKey ]; | |
| if ( cached ) { | |
| // Use the cached geometry if it exists | |
| pending.push( cached.promise ); | |
| } else { | |
| let geometryPromise; | |
| if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { | |
| // Use DRACO geometry if available | |
| geometryPromise = createDracoPrimitive( primitive ); | |
| } else { | |
| // Otherwise create a new geometry | |
| geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser ); | |
| } | |
| // Cache this geometry | |
| cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise }; | |
| pending.push( geometryPromise ); | |
| } | |
| } | |
| return Promise.all( pending ); | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes | |
| * @param {number} meshIndex | |
| * @return {Promise<Group|Mesh|SkinnedMesh>} | |
| */ | |
| loadMesh( meshIndex ) { | |
| const parser = this; | |
| const json = this.json; | |
| const extensions = this.extensions; | |
| const meshDef = json.meshes[ meshIndex ]; | |
| const primitives = meshDef.primitives; | |
| const pending = []; | |
| for ( let i = 0, il = primitives.length; i < il; i ++ ) { | |
| const material = primitives[ i ].material === undefined | |
| ? createDefaultMaterial( this.cache ) | |
| : this.getDependency( 'material', primitives[ i ].material ); | |
| pending.push( material ); | |
| } | |
| pending.push( parser.loadGeometries( primitives ) ); | |
| return Promise.all( pending ).then( function ( results ) { | |
| const materials = results.slice( 0, results.length - 1 ); | |
| const geometries = results[ results.length - 1 ]; | |
| const meshes = []; | |
| for ( let i = 0, il = geometries.length; i < il; i ++ ) { | |
| const geometry = geometries[ i ]; | |
| const primitive = primitives[ i ]; | |
| // 1. create Mesh | |
| let mesh; | |
| const material = materials[ i ]; | |
| if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || | |
| primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || | |
| primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || | |
| primitive.mode === undefined ) { | |
| // .isSkinnedMesh isn't in glTF spec. See ._markDefs() | |
| mesh = meshDef.isSkinnedMesh === true | |
| ? new SkinnedMesh( geometry, material ) | |
| : new Mesh( geometry, material ); | |
| if ( mesh.isSkinnedMesh === true ) { | |
| // normalize skin weights to fix malformed assets (see #15319) | |
| mesh.normalizeSkinWeights(); | |
| } | |
| if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { | |
| mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleStripDrawMode ); | |
| } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { | |
| mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleFanDrawMode ); | |
| } | |
| } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { | |
| mesh = new LineSegments( geometry, material ); | |
| } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { | |
| mesh = new Line( geometry, material ); | |
| } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { | |
| mesh = new LineLoop( geometry, material ); | |
| } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { | |
| mesh = new Points( geometry, material ); | |
| } else { | |
| throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); | |
| } | |
| if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { | |
| updateMorphTargets( mesh, meshDef ); | |
| } | |
| mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); | |
| assignExtrasToUserData( mesh, meshDef ); | |
| if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); | |
| parser.assignFinalMaterial( mesh ); | |
| meshes.push( mesh ); | |
| } | |
| for ( let i = 0, il = meshes.length; i < il; i ++ ) { | |
| parser.associations.set( meshes[ i ], { | |
| meshes: meshIndex, | |
| primitives: i | |
| } ); | |
| } | |
| if ( meshes.length === 1 ) { | |
| if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, meshes[ 0 ], meshDef ); | |
| return meshes[ 0 ]; | |
| } | |
| const group = new Group(); | |
| if ( meshDef.extensions ) addUnknownExtensionsToUserData( extensions, group, meshDef ); | |
| parser.associations.set( group, { meshes: meshIndex } ); | |
| for ( let i = 0, il = meshes.length; i < il; i ++ ) { | |
| group.add( meshes[ i ] ); | |
| } | |
| return group; | |
| } ); | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras | |
| * @param {number} cameraIndex | |
| * @return {Promise<THREE.Camera>} | |
| */ | |
| loadCamera( cameraIndex ) { | |
| let camera; | |
| const cameraDef = this.json.cameras[ cameraIndex ]; | |
| const params = cameraDef[ cameraDef.type ]; | |
| if ( ! params ) { | |
| console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); | |
| return; | |
| } | |
| if ( cameraDef.type === 'perspective' ) { | |
| camera = new PerspectiveCamera( MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); | |
| } else if ( cameraDef.type === 'orthographic' ) { | |
| camera = new OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); | |
| } | |
| if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name ); | |
| assignExtrasToUserData( camera, cameraDef ); | |
| return Promise.resolve( camera ); | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins | |
| * @param {number} skinIndex | |
| * @return {Promise<Skeleton>} | |
| */ | |
| loadSkin( skinIndex ) { | |
| const skinDef = this.json.skins[ skinIndex ]; | |
| const pending = []; | |
| for ( let i = 0, il = skinDef.joints.length; i < il; i ++ ) { | |
| pending.push( this._loadNodeShallow( skinDef.joints[ i ] ) ); | |
| } | |
| if ( skinDef.inverseBindMatrices !== undefined ) { | |
| pending.push( this.getDependency( 'accessor', skinDef.inverseBindMatrices ) ); | |
| } else { | |
| pending.push( null ); | |
| } | |
| return Promise.all( pending ).then( function ( results ) { | |
| const inverseBindMatrices = results.pop(); | |
| const jointNodes = results; | |
| // Note that bones (joint nodes) may or may not be in the | |
| // scene graph at this time. | |
| const bones = []; | |
| const boneInverses = []; | |
| for ( let i = 0, il = jointNodes.length; i < il; i ++ ) { | |
| const jointNode = jointNodes[ i ]; | |
| if ( jointNode ) { | |
| bones.push( jointNode ); | |
| const mat = new Matrix4(); | |
| if ( inverseBindMatrices !== null ) { | |
| mat.fromArray( inverseBindMatrices.array, i * 16 ); | |
| } | |
| boneInverses.push( mat ); | |
| } else { | |
| console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinDef.joints[ i ] ); | |
| } | |
| } | |
| return new Skeleton( bones, boneInverses ); | |
| } ); | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations | |
| * @param {number} animationIndex | |
| * @return {Promise<AnimationClip>} | |
| */ | |
| loadAnimation( animationIndex ) { | |
| const json = this.json; | |
| const parser = this; | |
| const animationDef = json.animations[ animationIndex ]; | |
| const animationName = animationDef.name ? animationDef.name : 'animation_' + animationIndex; | |
| const pendingNodes = []; | |
| const pendingInputAccessors = []; | |
| const pendingOutputAccessors = []; | |
| const pendingSamplers = []; | |
| const pendingTargets = []; | |
| for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) { | |
| const channel = animationDef.channels[ i ]; | |
| const sampler = animationDef.samplers[ channel.sampler ]; | |
| const target = channel.target; | |
| const name = target.node; | |
| const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; | |
| const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; | |
| if ( target.node === undefined ) continue; | |
| pendingNodes.push( this.getDependency( 'node', name ) ); | |
| pendingInputAccessors.push( this.getDependency( 'accessor', input ) ); | |
| pendingOutputAccessors.push( this.getDependency( 'accessor', output ) ); | |
| pendingSamplers.push( sampler ); | |
| pendingTargets.push( target ); | |
| } | |
| return Promise.all( [ | |
| Promise.all( pendingNodes ), | |
| Promise.all( pendingInputAccessors ), | |
| Promise.all( pendingOutputAccessors ), | |
| Promise.all( pendingSamplers ), | |
| Promise.all( pendingTargets ) | |
| ] ).then( function ( dependencies ) { | |
| const nodes = dependencies[ 0 ]; | |
| const inputAccessors = dependencies[ 1 ]; | |
| const outputAccessors = dependencies[ 2 ]; | |
| const samplers = dependencies[ 3 ]; | |
| const targets = dependencies[ 4 ]; | |
| const tracks = []; | |
| for ( let i = 0, il = nodes.length; i < il; i ++ ) { | |
| const node = nodes[ i ]; | |
| const inputAccessor = inputAccessors[ i ]; | |
| const outputAccessor = outputAccessors[ i ]; | |
| const sampler = samplers[ i ]; | |
| const target = targets[ i ]; | |
| if ( node === undefined ) continue; | |
| if ( node.updateMatrix ) { | |
| node.updateMatrix(); | |
| } | |
| const createdTracks = parser._createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ); | |
| if ( createdTracks ) { | |
| for ( let k = 0; k < createdTracks.length; k ++ ) { | |
| tracks.push( createdTracks[ k ] ); | |
| } | |
| } | |
| } | |
| return new AnimationClip( animationName, undefined, tracks ); | |
| } ); | |
| } | |
| createNodeMesh( nodeIndex ) { | |
| const json = this.json; | |
| const parser = this; | |
| const nodeDef = json.nodes[ nodeIndex ]; | |
| if ( nodeDef.mesh === undefined ) return null; | |
| return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { | |
| const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); | |
| // if weights are provided on the node, override weights on the mesh. | |
| if ( nodeDef.weights !== undefined ) { | |
| node.traverse( function ( o ) { | |
| if ( ! o.isMesh ) return; | |
| for ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { | |
| o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; | |
| } | |
| } ); | |
| } | |
| return node; | |
| } ); | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy | |
| * @param {number} nodeIndex | |
| * @return {Promise<Object3D>} | |
| */ | |
| loadNode( nodeIndex ) { | |
| const json = this.json; | |
| const parser = this; | |
| const nodeDef = json.nodes[ nodeIndex ]; | |
| const nodePending = parser._loadNodeShallow( nodeIndex ); | |
| const childPending = []; | |
| const childrenDef = nodeDef.children || []; | |
| for ( let i = 0, il = childrenDef.length; i < il; i ++ ) { | |
| childPending.push( parser.getDependency( 'node', childrenDef[ i ] ) ); | |
| } | |
| const skeletonPending = nodeDef.skin === undefined | |
| ? Promise.resolve( null ) | |
| : parser.getDependency( 'skin', nodeDef.skin ); | |
| return Promise.all( [ | |
| nodePending, | |
| Promise.all( childPending ), | |
| skeletonPending | |
| ] ).then( function ( results ) { | |
| const node = results[ 0 ]; | |
| const children = results[ 1 ]; | |
| const skeleton = results[ 2 ]; | |
| if ( skeleton !== null ) { | |
| // This full traverse should be fine because | |
| // child glTF nodes have not been added to this node yet. | |
| node.traverse( function ( mesh ) { | |
| if ( ! mesh.isSkinnedMesh ) return; | |
| mesh.bind( skeleton, _identityMatrix ); | |
| } ); | |
| } | |
| for ( let i = 0, il = children.length; i < il; i ++ ) { | |
| node.add( children[ i ] ); | |
| } | |
| return node; | |
| } ); | |
| } | |
| // ._loadNodeShallow() parses a single node. | |
| // skin and child nodes are created and added in .loadNode() (no '_' prefix). | |
| _loadNodeShallow( nodeIndex ) { | |
| const json = this.json; | |
| const extensions = this.extensions; | |
| const parser = this; | |
| // This method is called from .loadNode() and .loadSkin(). | |
| // Cache a node to avoid duplication. | |
| if ( this.nodeCache[ nodeIndex ] !== undefined ) { | |
| return this.nodeCache[ nodeIndex ]; | |
| } | |
| const nodeDef = json.nodes[ nodeIndex ]; | |
| // reserve node's name before its dependencies, so the root has the intended name. | |
| const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; | |
| const pending = []; | |
| const meshPromise = parser._invokeOne( function ( ext ) { | |
| return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); | |
| } ); | |
| if ( meshPromise ) { | |
| pending.push( meshPromise ); | |
| } | |
| if ( nodeDef.camera !== undefined ) { | |
| pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) { | |
| return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera ); | |
| } ) ); | |
| } | |
| parser._invokeAll( function ( ext ) { | |
| return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex ); | |
| } ).forEach( function ( promise ) { | |
| pending.push( promise ); | |
| } ); | |
| this.nodeCache[ nodeIndex ] = Promise.all( pending ).then( function ( objects ) { | |
| let node; | |
| // .isBone isn't in glTF spec. See ._markDefs | |
| if ( nodeDef.isBone === true ) { | |
| node = new Bone(); | |
| } else if ( objects.length > 1 ) { | |
| node = new Group(); | |
| } else if ( objects.length === 1 ) { | |
| node = objects[ 0 ]; | |
| } else { | |
| node = new Object3D(); | |
| } | |
| if ( node !== objects[ 0 ] ) { | |
| for ( let i = 0, il = objects.length; i < il; i ++ ) { | |
| node.add( objects[ i ] ); | |
| } | |
| } | |
| if ( nodeDef.name ) { | |
| node.userData.name = nodeDef.name; | |
| node.name = nodeName; | |
| } | |
| assignExtrasToUserData( node, nodeDef ); | |
| if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); | |
| if ( nodeDef.matrix !== undefined ) { | |
| const matrix = new Matrix4(); | |
| matrix.fromArray( nodeDef.matrix ); | |
| node.applyMatrix4( matrix ); | |
| } else { | |
| if ( nodeDef.translation !== undefined ) { | |
| node.position.fromArray( nodeDef.translation ); | |
| } | |
| if ( nodeDef.rotation !== undefined ) { | |
| node.quaternion.fromArray( nodeDef.rotation ); | |
| } | |
| if ( nodeDef.scale !== undefined ) { | |
| node.scale.fromArray( nodeDef.scale ); | |
| } | |
| } | |
| if ( ! parser.associations.has( node ) ) { | |
| parser.associations.set( node, {} ); | |
| } | |
| parser.associations.get( node ).nodes = nodeIndex; | |
| return node; | |
| } ); | |
| return this.nodeCache[ nodeIndex ]; | |
| } | |
| /** | |
| * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes | |
| * @param {number} sceneIndex | |
| * @return {Promise<Group>} | |
| */ | |
| loadScene( sceneIndex ) { | |
| const extensions = this.extensions; | |
| const sceneDef = this.json.scenes[ sceneIndex ]; | |
| const parser = this; | |
| // Loader returns Group, not Scene. | |
| // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 | |
| const scene = new Group(); | |
| if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); | |
| assignExtrasToUserData( scene, sceneDef ); | |
| if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); | |
| const nodeIds = sceneDef.nodes || []; | |
| const pending = []; | |
| for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { | |
| pending.push( parser.getDependency( 'node', nodeIds[ i ] ) ); | |
| } | |
| return Promise.all( pending ).then( function ( nodes ) { | |
| for ( let i = 0, il = nodes.length; i < il; i ++ ) { | |
| scene.add( nodes[ i ] ); | |
| } | |
| // Removes dangling associations, associations that reference a node that | |
| // didn't make it into the scene. | |
| const reduceAssociations = ( node ) => { | |
| const reducedAssociations = new Map(); | |
| for ( const [ key, value ] of parser.associations ) { | |
| if ( key instanceof Material || key instanceof Texture ) { | |
| reducedAssociations.set( key, value ); | |
| } | |
| } | |
| node.traverse( ( node ) => { | |
| const mappings = parser.associations.get( node ); | |
| if ( mappings != null ) { | |
| reducedAssociations.set( node, mappings ); | |
| } | |
| } ); | |
| return reducedAssociations; | |
| }; | |
| parser.associations = reduceAssociations( scene ); | |
| return scene; | |
| } ); | |
| } | |
| _createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ) { | |
| const tracks = []; | |
| const targetName = node.name ? node.name : node.uuid; | |
| const targetNames = []; | |
| if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { | |
| node.traverse( function ( object ) { | |
| if ( object.morphTargetInfluences ) { | |
| targetNames.push( object.name ? object.name : object.uuid ); | |
| } | |
| } ); | |
| } else { | |
| targetNames.push( targetName ); | |
| } | |
| let TypedKeyframeTrack; | |
| switch ( PATH_PROPERTIES[ target.path ] ) { | |
| case PATH_PROPERTIES.weights: | |
| TypedKeyframeTrack = NumberKeyframeTrack; | |
| break; | |
| case PATH_PROPERTIES.rotation: | |
| TypedKeyframeTrack = QuaternionKeyframeTrack; | |
| break; | |
| case PATH_PROPERTIES.position: | |
| case PATH_PROPERTIES.scale: | |
| TypedKeyframeTrack = VectorKeyframeTrack; | |
| break; | |
| default: | |
| switch ( outputAccessor.itemSize ) { | |
| case 1: | |
| TypedKeyframeTrack = NumberKeyframeTrack; | |
| break; | |
| case 2: | |
| case 3: | |
| default: | |
| TypedKeyframeTrack = VectorKeyframeTrack; | |
| break; | |
| } | |
| break; | |
| } | |
| const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; | |
| const outputArray = this._getArrayFromAccessor( outputAccessor ); | |
| for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { | |
| const track = new TypedKeyframeTrack( | |
| targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], | |
| inputAccessor.array, | |
| outputArray, | |
| interpolation | |
| ); | |
| // Override interpolation with custom factory method. | |
| if ( sampler.interpolation === 'CUBICSPLINE' ) { | |
| this._createCubicSplineTrackInterpolant( track ); | |
| } | |
| tracks.push( track ); | |
| } | |
| return tracks; | |
| } | |
| _getArrayFromAccessor( accessor ) { | |
| let outputArray = accessor.array; | |
| if ( accessor.normalized ) { | |
| const scale = getNormalizedComponentScale( outputArray.constructor ); | |
| const scaled = new Float32Array( outputArray.length ); | |
| for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { | |
| scaled[ j ] = outputArray[ j ] * scale; | |
| } | |
| outputArray = scaled; | |
| } | |
| return outputArray; | |
| } | |
| _createCubicSplineTrackInterpolant( track ) { | |
| track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { | |
| // A CUBICSPLINE keyframe in glTF has three output values for each input value, | |
| // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() | |
| // must be divided by three to get the interpolant's sampleSize argument. | |
| const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; | |
| return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); | |
| }; | |
| // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. | |
| track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; | |
| } | |
| } | |
| /** | |
| * @param {BufferGeometry} geometry | |
| * @param {GLTF.Primitive} primitiveDef | |
| * @param {GLTFParser} parser | |
| */ | |
| function computeBounds( geometry, primitiveDef, parser ) { | |
| const attributes = primitiveDef.attributes; | |
| const box = new Box3(); | |
| if ( attributes.POSITION !== undefined ) { | |
| const accessor = parser.json.accessors[ attributes.POSITION ]; | |
| const min = accessor.min; | |
| const max = accessor.max; | |
| // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. | |
| if ( min !== undefined && max !== undefined ) { | |
| box.set( | |
| new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), | |
| new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) | |
| ); | |
| if ( accessor.normalized ) { | |
| const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); | |
| box.min.multiplyScalar( boxScale ); | |
| box.max.multiplyScalar( boxScale ); | |
| } | |
| } else { | |
| console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); | |
| return; | |
| } | |
| } else { | |
| return; | |
| } | |
| const targets = primitiveDef.targets; | |
| if ( targets !== undefined ) { | |
| const maxDisplacement = new Vector3(); | |
| const vector = new Vector3(); | |
| for ( let i = 0, il = targets.length; i < il; i ++ ) { | |
| const target = targets[ i ]; | |
| if ( target.POSITION !== undefined ) { | |
| const accessor = parser.json.accessors[ target.POSITION ]; | |
| const min = accessor.min; | |
| const max = accessor.max; | |
| // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. | |
| if ( min !== undefined && max !== undefined ) { | |
| // we need to get max of absolute components because target weight is [-1,1] | |
| vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); | |
| vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); | |
| vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); | |
| if ( accessor.normalized ) { | |
| const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); | |
| vector.multiplyScalar( boxScale ); | |
| } | |
| // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative | |
| // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets | |
| // are used to implement key-frame animations and as such only two are active at a time - this results in very large | |
| // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. | |
| maxDisplacement.max( vector ); | |
| } else { | |
| console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); | |
| } | |
| } | |
| } | |
| // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. | |
| box.expandByVector( maxDisplacement ); | |
| } | |
| geometry.boundingBox = box; | |
| const sphere = new Sphere(); | |
| box.getCenter( sphere.center ); | |
| sphere.radius = box.min.distanceTo( box.max ) / 2; | |
| geometry.boundingSphere = sphere; | |
| } | |
| /** | |
| * @param {BufferGeometry} geometry | |
| * @param {GLTF.Primitive} primitiveDef | |
| * @param {GLTFParser} parser | |
| * @return {Promise<BufferGeometry>} | |
| */ | |
| function addPrimitiveAttributes( geometry, primitiveDef, parser ) { | |
| const attributes = primitiveDef.attributes; | |
| const pending = []; | |
| function assignAttributeAccessor( accessorIndex, attributeName ) { | |
| return parser.getDependency( 'accessor', accessorIndex ) | |
| .then( function ( accessor ) { | |
| geometry.setAttribute( attributeName, accessor ); | |
| } ); | |
| } | |
| for ( const gltfAttributeName in attributes ) { | |
| const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); | |
| // Skip attributes already provided by e.g. Draco extension. | |
| if ( threeAttributeName in geometry.attributes ) continue; | |
| pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); | |
| } | |
| if ( primitiveDef.indices !== undefined && ! geometry.index ) { | |
| const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { | |
| geometry.setIndex( accessor ); | |
| } ); | |
| pending.push( accessor ); | |
| } | |
| if ( ColorManagement.workingColorSpace !== LinearSRGBColorSpace && 'COLOR_0' in attributes ) { | |
| console.warn( `THREE.GLTFLoader: Converting vertex colors from "srgb-linear" to "${ColorManagement.workingColorSpace}" not supported.` ); | |
| } | |
| assignExtrasToUserData( geometry, primitiveDef ); | |
| computeBounds( geometry, primitiveDef, parser ); | |
| return Promise.all( pending ).then( function () { | |
| return primitiveDef.targets !== undefined | |
| ? addMorphTargets( geometry, primitiveDef.targets, parser ) | |
| : geometry; | |
| } ); | |
| } | |
| export { GLTFLoader }; | |