From 017d34812c2fd0254f4bbf936b1ef7ac7dba0810 Mon Sep 17 00:00:00 2001 From: Takahiro Date: Fri, 17 Jan 2020 16:53:05 -0800 Subject: [PATCH] GLTFLoader: Introduce plugin system --- examples/js/loaders/GLTFLoader.js | 1345 +++++++++++++-------------- examples/jsm/loaders/GLTFLoader.js | 1349 ++++++++++++++-------------- 2 files changed, 1366 insertions(+), 1328 deletions(-) diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index 8ee29dc4e0b953..057d9a4e246791 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -12,8 +12,16 @@ THREE.GLTFLoader = ( function () { THREE.Loader.call( this, manager ); - this.dracoLoader = null; - this.ddsLoader = null; + this.plugins = { + extensions: {}, + generics: [] + }; + + this.registerPlugin( new GLTFMaterialsPbrSpecularGlossinessExtension() ); + this.registerPlugin( new GLTFMaterialsUnlitExtension() ); + this.registerPlugin( new GLTFMeshQuantizationExtension() ); + this.registerPlugin( new GLTFLightsExtension() ); + this.registerPlugin( new GLTFTextureTransformExtension() ); } @@ -98,14 +106,36 @@ THREE.GLTFLoader = ( function () { setDRACOLoader: function ( dracoLoader ) { - this.dracoLoader = dracoLoader; + this.registerPlugin( new GLTFDracoMeshCompressionExtension( dracoLoader ) ); + return this; + + }, + + registerPlugin: function ( plugin ) { + + var extensionName = plugin.extension; + + if ( extensionName ) { + + this.plugins.extensions[ extensionName ] = plugin; + + } else { + + if ( ! this.plugins.generics.includes( plugin ) ) { + + this.plugins.generics.push( plugin ); + + } + + } + return this; }, setDDSLoader: function ( ddsLoader ) { - this.ddsLoader = ddsLoader; + this.registerPlugin( new GLTFTextureDDSExtension( ddsLoader ) ); return this; }, @@ -127,7 +157,9 @@ THREE.GLTFLoader = ( function () { try { - extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); + // TODO: Binary is in core spec, not extension. + // Maybe better to rename and to handle out of plugin system. + this.registerPlugin( new GLTFBinaryExtension( data ) ); } catch ( error ) { @@ -136,7 +168,7 @@ THREE.GLTFLoader = ( function () { } - content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; + content = this.plugins.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; } else { @@ -162,43 +194,9 @@ THREE.GLTFLoader = ( function () { var extensionName = json.extensionsUsed[ i ]; var extensionsRequired = json.extensionsRequired || []; - switch ( extensionName ) { - - case EXTENSIONS.KHR_LIGHTS_PUNCTUAL: - extensions[ extensionName ] = new GLTFLightsExtension( json ); - break; - - case EXTENSIONS.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); - break; - - case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: - extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); - break; - - case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); - break; - - case EXTENSIONS.MSFT_TEXTURE_DDS: - extensions[ extensionName ] = new GLTFTextureDDSExtension( this.ddsLoader ); - 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 && this.plugins.extensions[ extensionName ] === undefined ) { - if ( extensionsRequired.indexOf( extensionName ) >= 0 ) { - - console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); - - } + console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); } @@ -206,7 +204,7 @@ THREE.GLTFLoader = ( function () { } - var parser = new GLTFParser( json, extensions, { + var parser = new GLTFParser( json, this.plugins, { path: path || this.resourcePath || '', crossOrigin: this.crossOrigin, @@ -285,77 +283,102 @@ THREE.GLTFLoader = ( function () { } - this.name = EXTENSIONS.MSFT_TEXTURE_DDS; + this.extension = EXTENSIONS.MSFT_TEXTURE_DDS; this.ddsLoader = ddsLoader; } + GLTFTextureDDSExtension.prototype = { + + constructor: GLTFTextureDDSExtension, + + onTexture: function ( textureDef, parser ) { + + var extensions = textureDef.extensions; + var json = parser.json; + var imageDef = json.images[ extensions[ this.extension ].source ]; + return parser.loadTextureFile( imageDef, this.ddsLoader ); + + } + + }; + /** * Punctual Lights Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ - function GLTFLightsExtension( json ) { - - this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; + function GLTFLightsExtension() { - var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {}; - this.lightDefs = extension.lights || []; + this.extension = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; } - GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) { + GLTFLightsExtension.prototype = { - var lightDef = this.lightDefs[ lightIndex ]; - var lightNode; + constructor: GLTFLightsExtension, - var color = new THREE.Color( 0xffffff ); - if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); + onNode: function ( nodeDef, parser ) { - var range = lightDef.range !== undefined ? lightDef.range : 0; + var extensions = nodeDef.extensions || {}; + var json = parser.json; + var lightExtension = json.extensions[ this.extension ] || {}; + var lightDefs = lightExtension.lights || []; + var lightIndex = extensions[ this.extension ].light; + var lightDef = lightDefs[ lightIndex ]; - switch ( lightDef.type ) { + var lightNode; - case 'directional': - lightNode = new THREE.DirectionalLight( color ); - lightNode.target.position.set( 0, 0, - 1 ); - lightNode.add( lightNode.target ); - break; + var color = new THREE.Color( 0xffffff ); - case 'point': - lightNode = new THREE.PointLight( color ); - lightNode.distance = range; - break; + if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); - case 'spot': - lightNode = new THREE.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; + var range = lightDef.range !== undefined ? lightDef.range : 0; - default: - throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' ); + switch ( lightDef.type ) { - } + case 'directional': + lightNode = new THREE.DirectionalLight( color ); + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + case 'point': + lightNode = new THREE.PointLight( color ); + lightNode.distance = range; + break; - // 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 ); + case 'spot': + lightNode = new THREE.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; - lightNode.decay = 2; + default: + throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' ); + + } - if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; + // 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.name = lightDef.name || ( 'light_' + lightIndex ); + lightNode.decay = 2; - return Promise.resolve( lightNode ); + if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; + + lightNode.name = lightDef.name || ( 'light_' + lightIndex ); + + return Promise.resolve( lightNode ); + + } }; @@ -366,46 +389,51 @@ THREE.GLTFLoader = ( function () { */ function GLTFMaterialsUnlitExtension() { - this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; + this.extension = EXTENSIONS.KHR_MATERIALS_UNLIT; } - GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () { + GLTFMaterialsUnlitExtension.prototype = { - return THREE.MeshBasicMaterial; + constructor: GLTFMaterialsUnlitExtension, - }; + onMaterial: function ( materialDef, parser ) { + + var materialParams = {}; + var pending = []; - GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) { + materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; - var pending = []; + var metallicRoughness = materialDef.pbrMetallicRoughness; - materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; + if ( metallicRoughness ) { - var metallicRoughness = materialDef.pbrMetallicRoughness; + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - if ( metallicRoughness ) { + var array = metallicRoughness.baseColorFactor; - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; - var array = metallicRoughness.baseColorFactor; + } - materialParams.color.fromArray( array ); - materialParams.opacity = array[ 3 ]; + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); + + } } - if ( metallicRoughness.baseColorTexture !== undefined ) { + return Promise.all( pending ).then( function () { - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); + return new THREE.MeshBasicMaterial( materialParams ); - } + } ); } - return Promise.all( pending ); - }; /* BINARY EXTENSION */ @@ -415,7 +443,7 @@ THREE.GLTFLoader = ( function () { function GLTFBinaryExtension( data ) { - this.name = EXTENSIONS.KHR_BINARY_GLTF; + this.extension = EXTENSIONS.KHR_BINARY_GLTF; this.content = null; this.body = null; @@ -479,7 +507,7 @@ THREE.GLTFLoader = ( function () { * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression */ - function GLTFDracoMeshCompressionExtension( json, dracoLoader ) { + function GLTFDracoMeshCompressionExtension( dracoLoader ) { if ( ! dracoLoader ) { @@ -487,69 +515,78 @@ THREE.GLTFLoader = ( function () { } - this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; - this.json = json; + this.extension = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; this.dracoLoader = dracoLoader; this.dracoLoader.preload(); } - GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { + GLTFDracoMeshCompressionExtension.prototype = { - var json = this.json; - var dracoLoader = this.dracoLoader; - var bufferViewIndex = primitive.extensions[ this.name ].bufferView; - var gltfAttributeMap = primitive.extensions[ this.name ].attributes; - var threeAttributeMap = {}; - var attributeNormalizedMap = {}; - var attributeTypeMap = {}; + constructor: GLTFDracoMeshCompressionExtension, - for ( var attributeName in gltfAttributeMap ) { + onGeometry: function ( primitive, parser ) { - var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + var json = parser.json; + var dracoLoader = this.dracoLoader; + var bufferViewIndex = primitive.extensions[ this.extension ].bufferView; + var gltfAttributeMap = primitive.extensions[ this.extension ].attributes; + var threeAttributeMap = {}; + var attributeNormalizedMap = {}; + var attributeTypeMap = {}; - threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; + for ( var attributeName in gltfAttributeMap ) { - } + var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; + + } - for ( attributeName in primitive.attributes ) { + for ( attributeName in primitive.attributes ) { - var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - if ( gltfAttributeMap[ attributeName ] !== undefined ) { + if ( gltfAttributeMap[ attributeName ] !== undefined ) { - var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; + var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - attributeTypeMap[ threeAttributeName ] = componentType; - attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; + attributeTypeMap[ threeAttributeName ] = componentType; + attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; + + } } - } + return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { - return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { + return new Promise( function ( resolve ) { - return new Promise( function ( resolve ) { + dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { - dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { + for ( var attributeName in geometry.attributes ) { - for ( var attributeName in geometry.attributes ) { + var attribute = geometry.attributes[ attributeName ]; + var normalized = attributeNormalizedMap[ attributeName ]; - var attribute = geometry.attributes[ attributeName ]; - var normalized = attributeNormalizedMap[ attributeName ]; + if ( normalized !== undefined ) attribute.normalized = normalized; - if ( normalized !== undefined ) attribute.normalized = normalized; + } - } + resolve( geometry ); + + }, threeAttributeMap, attributeTypeMap ); - resolve( geometry ); + } ).then( function ( geometry ) { - }, threeAttributeMap, attributeTypeMap ); + return addPrimitiveAttributes( geometry, primitive, parser ); + + } ); } ); - } ); + }, }; @@ -560,438 +597,305 @@ THREE.GLTFLoader = ( function () { */ function GLTFTextureTransformExtension() { - this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; + this.extension = EXTENSIONS.KHR_TEXTURE_TRANSFORM; } - GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) { - - texture = texture.clone(); - - 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 ); - - } - - if ( transform.texCoord !== undefined ) { - - console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); + GLTFTextureTransformExtension.prototype = { - } + constructor: GLTFTextureTransformExtension, - texture.needsUpdate = true; + onAfterMap: function ( texture, textureDef, parser ) { - return texture; + var extensions = textureDef.extensions; + var transform = extensions[ this.name ]; - }; + texture = texture.clone(); - /** - * Specular-Glossiness Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness - */ - function GLTFMaterialsPbrSpecularGlossinessExtension() { + if ( transform.offset !== undefined ) { - return { - - name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, - - specularGlossinessParams: [ - 'color', - 'map', - 'lightMap', - 'lightMapIntensity', - 'aoMap', - 'aoMapIntensity', - 'emissive', - 'emissiveIntensity', - 'emissiveMap', - 'bumpMap', - 'bumpScale', - 'normalMap', - 'normalMapType', - 'displacementMap', - 'displacementScale', - 'displacementBias', - 'specularMap', - 'specular', - 'glossinessMap', - 'glossiness', - 'alphaMap', - 'envMap', - 'envMapIntensity', - 'refractionRatio', - ], - - getMaterialType: function () { - - return THREE.ShaderMaterial; + texture.offset.fromArray( transform.offset ); - }, + } - extendParams: function ( materialParams, materialDef, parser ) { - - var pbrSpecularGlossiness = materialDef.extensions[ this.name ]; - - var shader = THREE.ShaderLib[ 'standard' ]; - - var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); - - var specularMapParsFragmentChunk = [ - '#ifdef USE_SPECULARMAP', - ' uniform sampler2D specularMap;', - '#endif' - ].join( '\n' ); - - var glossinessMapParsFragmentChunk = [ - '#ifdef USE_GLOSSINESSMAP', - ' uniform sampler2D glossinessMap;', - '#endif' - ].join( '\n' ); - - var specularMapFragmentChunk = [ - 'vec3 specularFactor = specular;', - '#ifdef USE_SPECULARMAP', - ' vec4 texelSpecular = texture2D( specularMap, vUv );', - ' texelSpecular = sRGBToLinear( texelSpecular );', - ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', - ' specularFactor *= texelSpecular.rgb;', - '#endif' - ].join( '\n' ); - - var glossinessMapFragmentChunk = [ - 'float glossinessFactor = glossiness;', - '#ifdef USE_GLOSSINESSMAP', - ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', - ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', - ' glossinessFactor *= texelGlossiness.a;', - '#endif' - ].join( '\n' ); - - var lightPhysicalFragmentChunk = [ - 'PhysicalMaterial material;', - 'material.diffuseColor = diffuseColor.rgb;', - 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );', - 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );', - 'material.specularRoughness = max( 1.0 - glossinessFactor, 0.0525 );// 0.0525 corresponds to the base mip of a 256 cubemap.', - 'material.specularRoughness += geometryRoughness;', - 'material.specularRoughness = min( material.specularRoughness, 1.0 );', - 'material.specularColor = specularFactor.rgb;', - ].join( '\n' ); - - var fragmentShader = shader.fragmentShader - .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) - .replace( 'uniform float metalness;', 'uniform float glossiness;' ) - .replace( '#include ', specularMapParsFragmentChunk ) - .replace( '#include ', glossinessMapParsFragmentChunk ) - .replace( '#include ', specularMapFragmentChunk ) - .replace( '#include ', glossinessMapFragmentChunk ) - .replace( '#include ', lightPhysicalFragmentChunk ); - - delete uniforms.roughness; - delete uniforms.metalness; - delete uniforms.roughnessMap; - delete uniforms.metalnessMap; - - uniforms.specular = { value: new THREE.Color().setHex( 0x111111 ) }; - uniforms.glossiness = { value: 0.5 }; - uniforms.specularMap = { value: null }; - uniforms.glossinessMap = { value: null }; - - materialParams.vertexShader = shader.vertexShader; - materialParams.fragmentShader = fragmentShader; - materialParams.uniforms = uniforms; - materialParams.defines = { 'STANDARD': '' }; - - materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; + if ( transform.rotation !== undefined ) { - var pending = []; + texture.rotation = transform.rotation; - if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + } - var array = pbrSpecularGlossiness.diffuseFactor; + if ( transform.scale !== undefined ) { - materialParams.color.fromArray( array ); - materialParams.opacity = array[ 3 ]; + texture.repeat.fromArray( transform.scale ); - } + } - if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + if ( transform.texCoord !== undefined ) { - pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); + console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); - } + } - materialParams.emissive = new THREE.Color( 0.0, 0.0, 0.0 ); - materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; - materialParams.specular = new THREE.Color( 1.0, 1.0, 1.0 ); + texture.needsUpdate = true; - if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { + return texture; - materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + } - } + }; - if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + function GLTFMeshStandardSGMaterial( params ) { + + THREE.MeshStandardMaterial.call( this ); + + var specularMapParsFragmentChunk = [ + '#ifdef USE_SPECULARMAP', + ' uniform sampler2D specularMap;', + '#endif' + ].join( '\n' ); + + var glossinessMapParsFragmentChunk = [ + '#ifdef USE_GLOSSINESSMAP', + ' uniform sampler2D glossinessMap;', + '#endif' + ].join( '\n' ); + + var specularMapFragmentChunk = [ + 'vec3 specularFactor = specular;', + '#ifdef USE_SPECULARMAP', + ' vec4 texelSpecular = texture2D( specularMap, vUv );', + ' texelSpecular = sRGBToLinear( texelSpecular );', + ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' specularFactor *= texelSpecular.rgb;', + '#endif' + ].join( '\n' ); + + var glossinessMapFragmentChunk = [ + 'float glossinessFactor = glossiness;', + '#ifdef USE_GLOSSINESSMAP', + ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', + ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' glossinessFactor *= texelGlossiness.a;', + '#endif' + ].join( '\n' ); + + var lightPhysicalFragmentChunk = [ + 'PhysicalMaterial material;', + 'material.diffuseColor = diffuseColor.rgb;', + 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', + 'material.specularColor = specularFactor.rgb;', + ].join( '\n' ); + + var uniforms = { + specular: { value: new THREE.Color().setHex( 0xffffff ) }, + glossiness: { value: 1 }, + specularMap: { value: null }, + glossinessMap: { value: null } + }; - var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; - pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); - pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); + this._extraUniforms = uniforms; - } + this.onBeforeCompile = function ( shader ) { - return Promise.all( pending ); + for ( var uniformName in uniforms ) { - }, + shader.uniforms[ uniformName ] = uniforms[ uniformName ]; - createMaterial: function ( params ) { + } - // setup material properties based on MeshStandardMaterial for Specular-Glossiness + shader.fragmentShader = shader.fragmentShader.replace( 'uniform float roughness;', 'uniform vec3 specular;' ); + shader.fragmentShader = shader.fragmentShader.replace( 'uniform float metalness;', 'uniform float glossiness;' ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapParsFragmentChunk ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapParsFragmentChunk ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapFragmentChunk ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapFragmentChunk ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', lightPhysicalFragmentChunk ); - var material = new THREE.ShaderMaterial( { - defines: params.defines, - vertexShader: params.vertexShader, - fragmentShader: params.fragmentShader, - uniforms: params.uniforms, - fog: true, - lights: true, - opacity: params.opacity, - transparent: params.transparent - } ); + }; - material.isGLTFSpecularGlossinessMaterial = true; + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; - material.color = params.color; + this.setValues( params ); - material.map = params.map === undefined ? null : params.map; + } - material.lightMap = null; - material.lightMapIntensity = 1.0; + GLTFMeshStandardSGMaterial.prototype = Object.assign( Object.create( THREE.MeshStandardMaterial.prototype ), { - material.aoMap = params.aoMap === undefined ? null : params.aoMap; - material.aoMapIntensity = 1.0; + constructor: GLTFMeshStandardSGMaterial, - material.emissive = params.emissive; - material.emissiveIntensity = 1.0; - material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap; + isGLTFMeshStandardSGMaterial: true, - material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap; - material.bumpScale = 1; + copy: function ( source ) { - material.normalMap = params.normalMap === undefined ? null : params.normalMap; - material.normalMapType = THREE.TangentSpaceNormalMap; + THREE.MeshStandardMaterial.prototype.copy.call( this, source ); - if ( params.normalScale ) material.normalScale = params.normalScale; + this.specularMap = source.specularMap; + this.specular.copy( source.specular ); + this.glossinessMap = source.glossinessMap; + this.glossiness = source.glossiness; - material.displacementMap = null; - material.displacementScale = 1; - material.displacementBias = 0; + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; - material.specularMap = params.specularMap === undefined ? null : params.specularMap; - material.specular = params.specular; + return this; - material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap; - material.glossiness = params.glossiness; + } - material.alphaMap = null; + } ); - material.envMap = params.envMap === undefined ? null : params.envMap; - material.envMapIntensity = 1.0; + Object.defineProperties( GLTFMeshStandardSGMaterial.prototype, { - material.refractionRatio = 0.98; + specular: { - material.extensions.derivatives = true; + get: function () { - return material; + return this._extraUniforms.specular.value; }, - /** - * Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can - * copy only properties it knows about or inherits, and misses many properties that would - * normally be defined by MeshStandardMaterial. - * - * This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of - * loading a glTF model, but cloning later (e.g. by the user) would require these changes - * AND also updating `.onBeforeRender` on the parent mesh. - * - * @param {THREE.ShaderMaterial} source - * @return {THREE.ShaderMaterial} - */ - cloneMaterial: function ( source ) { + set: function ( v ) { - var target = source.clone(); + this._extraUniforms.specular.value = v; - target.isGLTFSpecularGlossinessMaterial = true; - - var params = this.specularGlossinessParams; + } - for ( var i = 0, il = params.length; i < il; i ++ ) { + }, - var value = source[ params[ i ] ]; - target[ params[ i ] ] = ( value && value.isColor ) ? value.clone() : value; + glossiness: { - } + get: function () { - return target; + return this._extraUniforms.glossiness.value; }, - // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. - refreshUniforms: function ( renderer, scene, camera, geometry, material ) { - - if ( material.isGLTFSpecularGlossinessMaterial !== true ) { - - return; + set: function ( v ) { - } - - var uniforms = material.uniforms; - var defines = material.defines; - - uniforms.opacity.value = material.opacity; - - uniforms.diffuse.value.copy( material.color ); - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); + this._extraUniforms.glossiness.value = v; - uniforms.map.value = material.map; - uniforms.specularMap.value = material.specularMap; - uniforms.alphaMap.value = material.alphaMap; + } - uniforms.lightMap.value = material.lightMap; - uniforms.lightMapIntensity.value = material.lightMapIntensity; + }, - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; + glossinessMap: { - // uv repeat and offset setting priorities - // 1. color map - // 2. specular map - // 3. normal map - // 4. bump map - // 5. alpha map - // 6. emissive map + get: function () { - var uvScaleMap; + this._extraUniforms.glossinessMap.value; - if ( material.map ) { + }, - uvScaleMap = material.map; + set: function ( v ) { - } else if ( material.specularMap ) { + this._extraUniforms.glossinessMap.value = v; - uvScaleMap = material.specularMap; + if ( v ) { - } else if ( material.displacementMap ) { + this.defines.USE_GLOSSINESSMAP = ''; + // set USE_ROUGHNESSMAP to enable vUv + this.defines.USE_ROUGHNESSMAP = ''; - uvScaleMap = material.displacementMap; + } else { - } else if ( material.normalMap ) { + delete this.defines.USE_ROUGHNESSMAP; + delete this.defines.USE_GLOSSINESSMAP; - uvScaleMap = material.normalMap; + } - } else if ( material.bumpMap ) { + } - uvScaleMap = material.bumpMap; + } - } else if ( material.glossinessMap ) { + } ); - uvScaleMap = material.glossinessMap; + /** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness + */ + function GLTFMaterialsPbrSpecularGlossinessExtension() { - } else if ( material.alphaMap ) { + this.extension = EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS; - uvScaleMap = material.alphaMap; + } - } else if ( material.emissiveMap ) { + GLTFMaterialsPbrSpecularGlossinessExtension.prototype = { - uvScaleMap = material.emissiveMap; + constructor: GLTFMaterialsPbrSpecularGlossinessExtension, - } + onAfterMaterial: function( material, materialDef, parser ) { - if ( uvScaleMap !== undefined ) { + // Create Specular-Glossiness material from MeshStandardMaterial instance. + // Rewrite to onMaterial if you want to avoid MeshStandardMaterial creation cost. - // backwards compatibility - if ( uvScaleMap.isWebGLRenderTarget ) { + var extensions = materialDef.extensions; + var pbrSpecularGlossiness = extensions[ this.extension ]; - uvScaleMap = uvScaleMap.texture; + var newMaterial = new GLTFMeshStandardSGMaterial(); - } + THREE.MeshStandardMaterial.prototype.copy.call( newMaterial, material ); - if ( uvScaleMap.matrixAutoUpdate === true ) { + delete newMaterial.metalness; + delete newMaterial.roughness; + delete newMaterial.metalnessMap; + delete newMaterial.roughnessMap; - uvScaleMap.updateMatrix(); + var pending = []; - } + newMaterial.color.setRGB( 1.0, 1.0, 1.0 ); + newMaterial.opacity = 1.0; - uniforms.uvTransform.value.copy( uvScaleMap.matrix ); + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { - } + var array = pbrSpecularGlossiness.diffuseFactor; - if ( material.envMap ) { + newMaterial.color.fromArray( array ); + newMaterial.opacity = array[ 3 ]; - uniforms.envMap.value = material.envMap; - uniforms.envMapIntensity.value = material.envMapIntensity; + } - // don't flip CubeTexture envMaps, flip everything else: - // WebGLCubeRenderTarget will be flipped for backwards compatibility - // WebGLCubeRenderTarget.texture will be flipped because it's a Texture and NOT a CubeTexture - // this check must be handled differently, or removed entirely, if WebGLCubeRenderTarget uses a CubeTexture in the future - uniforms.flipEnvMap.value = material.envMap.isCubeTexture ? - 1 : 1; + newMaterial.map = null; - uniforms.reflectivity.value = material.reflectivity; - uniforms.refractionRatio.value = material.refractionRatio; + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { - uniforms.maxMipLevel.value = renderer.properties.get( material.envMap ).__maxMipLevel; + pending.push( parser.assignTexture( newMaterial, 'map', pbrSpecularGlossiness.diffuseTexture ) ); - } + } - uniforms.specular.value.copy( material.specular ); - uniforms.glossiness.value = material.glossiness; + newMaterial.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; + newMaterial.specular.setRGB( 1.0, 1.0, 1.0 ); - uniforms.glossinessMap.value = material.glossinessMap; + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { - uniforms.emissiveMap.value = material.emissiveMap; - uniforms.bumpMap.value = material.bumpMap; - uniforms.normalMap.value = material.normalMap; + newMaterial.specular.fromArray( pbrSpecularGlossiness.specularFactor ); - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; + } - if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { - defines.USE_GLOSSINESSMAP = ''; - // set USE_ROUGHNESSMAP to enable vUv - defines.USE_ROUGHNESSMAP = ''; + var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; + pending.push( parser.assignTexture( newMaterial, 'glossinessMap', specGlossMapDef ) ); + pending.push( parser.assignTexture( newMaterial, 'specularMap', specGlossMapDef ) ); - } + } - if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { + return Promise.all( pending ).then( function () { - delete defines.USE_GLOSSINESSMAP; - delete defines.USE_ROUGHNESSMAP; + if ( newMaterial.map ) newMaterial.map.encoding = THREE.sRGBEncoding; + if ( newMaterial.specularMap ) newMaterial.specularMap.encoding = THREE.sRGBEncoding; - } + return newMaterial; - } + } ); - }; + } - } + }; /** * Mesh Quantization Extension @@ -1000,7 +904,7 @@ THREE.GLTFLoader = ( function () { */ function GLTFMeshQuantizationExtension() { - this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; + this.extension = EXTENSIONS.KHR_MESH_QUANTIZATION; } @@ -1394,6 +1298,7 @@ THREE.GLTFLoader = ( function () { if ( dracoExtension ) { + // TODO: Can we move this under GLTFDracoMeshCompressionExtension? geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices + ':' + createAttributesKey( dracoExtension.attributes ); @@ -1426,10 +1331,10 @@ THREE.GLTFLoader = ( function () { /* GLTF PARSER */ - function GLTFParser( json, extensions, options ) { + function GLTFParser( json, plugins, options ) { this.json = json || {}; - this.extensions = extensions || {}; + this.plugins = plugins || { extensions: {}, generics: [] }; this.options = options || {}; // loader object cache @@ -1456,7 +1361,7 @@ THREE.GLTFLoader = ( function () { var parser = this; var json = this.json; - var extensions = this.extensions; + var extensions = this.plugins.extensions; // Clear the loader cache this.cache.removeAll(); @@ -1464,13 +1369,19 @@ THREE.GLTFLoader = ( function () { // Mark the special nodes/meshes in json for efficient parse this.markDefs(); - Promise.all( [ + this._onBefore( 'GLTF', json ).then( function ( json ) { - this.getDependencies( 'scene' ), - this.getDependencies( 'animation' ), - this.getDependencies( 'camera' ), + parser.json = json; - ] ).then( function ( dependencies ) { + return Promise.all( [ + + parser.getDependencies( 'scene' ), + parser.getDependencies( 'animation' ), + parser.getDependencies( 'camera' ), + + ] ); + + } ).then( function ( dependencies ) { var result = { scene: dependencies[ 0 ][ json.scene || 0 ], @@ -1486,7 +1397,7 @@ THREE.GLTFLoader = ( function () { assignExtrasToUserData( result, json ); - onLoad( result ); + parser._onAfter( 'GLTF', result, json ).then( onLoad ); } ).catch( onError ); @@ -1555,6 +1466,161 @@ THREE.GLTFLoader = ( function () { }; + /** + * @param {string} key + * @param {GLTF.definition} def + * @return {Promise}} + */ + GLTFParser.prototype._onBefore = function ( key, def ) { + + var parser = this; + var functionName = 'onBefore' + key; + var plugins = this.plugins || {}; + var extensionPlugins = plugins.extensions || {}; + var genericPlugins = plugins.generics || {}; + var extensions = def.extensions || {}; + var pending = Promise.resolve( def ); + + for ( var extensionName in extensions ) { + + var plugin = extensionPlugins[ extensionName ]; + + if ( plugin === undefined || plugin[ functionName ] === undefined ) { + + continue; + + } + + pending = pending.then( function ( plugin, def ) { + + return plugin[ functionName ]( def, parser ); + + }.bind( null, plugin ) ); + + } + + for ( var plugin of genericPlugins ) { + + if ( plugin[ functionName ] === undefined ) { + + continue; + + } + + pending = pending.then( function ( plugin, def ) { + + return plugin[ functionName ]( def, parser ); + + }.bind( null, plugin ) ); + + } + + return pending; + + }; + + /** + * @param {string} key + * @param {Object} object + * @param {GLTF.definition} def + * @return {Promise}} + */ + GLTFParser.prototype._onAfter = function ( key, object, def ) { + + var parser = this; + var functionName = 'onAfter' + key; + var plugins = this.plugins || {}; + var extensionPlugins = plugins.extensions || {}; + var genericPlugins = plugins.generics || []; + var extensions = def.extensions || {}; + var pending = Promise.resolve( object ); + + for ( var extensionName in extensions ) { + + var plugin = extensionPlugins[ extensionName ]; + + if ( plugin === undefined || plugin[ functionName ] === undefined ) { + + continue; + + } + + pending = pending.then( function ( plugin, object ) { + + return plugin[ functionName ]( object, def, parser ); + + }.bind( null, plugin ) ); + + } + + for ( var plugin of genericPlugins ) { + + if ( plugin[ functionName ] === undefined ) { + + continue; + + } + + pending = pending.then( function ( plugin, object ) { + + return plugin[ functionName ]( object, def, parser ); + + }.bind( null, plugin ) ); + + } + + return pending; + + }; + + /** + * @param {string} key + * @param {GLTF.definition} def + * @return {Promise|null}} + */ + GLTFParser.prototype._on = function ( key, def ) { + + var parser = this; + var functionName = 'on' + key; + var plugins = this.plugins || {}; + var extensionPlugins = plugins.extensions || {}; + var genericPlugins = plugins.generics || []; + var extensions = def.extensions || {}; + + for ( var extensionName in extensions ) { + + var plugin = extensionPlugins[ extensionName ]; + + if ( plugin === undefined || plugin[ functionName ] === undefined ) { + + continue; + + } + + var result = plugin[ functionName ]( def, parser ); + + if ( result ) return result; + + } + + for ( var plugin of genericPlugins ) { + + if ( plugin[ functionName ] === undefined ) { + + continue; + + } + + var result = plugin[ functionName ]( def, parser ); + + if ( result ) return result; + + } + + return null; + + }; + /** * Requests the specified dependency asynchronously, with caching. * @param {string} type @@ -1563,6 +1629,8 @@ THREE.GLTFLoader = ( function () { */ GLTFParser.prototype.getDependency = function ( type, index ) { + var parser = this; + var json = parser.json; var cacheKey = type + ':' + index; var dependency = this.cache.get( cacheKey ); @@ -1571,51 +1639,91 @@ THREE.GLTFLoader = ( function () { switch ( type ) { case 'scene': - dependency = this.loadScene( index ); + dependency = this.loadScene( index ).then( function ( scene ) { + + return parser._onAfter( 'Scene', scene, json.scenes[ index ] ); + + } ); break; case 'node': - dependency = this.loadNode( index ); + dependency = this.loadNode( index ).then( function ( node ) { + + return parser._onAfter( 'Node', node, json.nodes[ index ] ); + + } ); break; case 'mesh': - dependency = this.loadMesh( index ); + dependency = this.loadMesh( index ).then( function ( mesh ) { + + return parser._onAfter( 'Mesh', mesh, json.meshes[ index ] ); + + } ); break; case 'accessor': - dependency = this.loadAccessor( index ); + dependency = this.loadAccessor( index ).then( function ( accessor ) { + + return parser._onAfter( 'Accessor', accessor, json.accessors[ index ] ); + + } ); break; case 'bufferView': - dependency = this.loadBufferView( index ); + dependency = this.loadBufferView( index ).then( function ( bufferView ) { + + return parser._onAfter( 'BufferView', bufferView, json.bufferViews[ index ] ); + + } ); break; case 'buffer': - dependency = this.loadBuffer( index ); + dependency = this.loadBuffer( index ).then( function ( buffer ) { + + return parser._onAfter( 'Buffer', buffer, json.buffers[ index ] ); + + } ); break; case 'material': - dependency = this.loadMaterial( index ); + dependency = this.loadMaterial( index ).then( function ( material ) { + + return parser._onAfter( 'Material', material, json.materials[ index ] ); + + } ); break; case 'texture': - dependency = this.loadTexture( index ); + dependency = this.loadTexture( index ).then( function ( texture ) { + + return parser._onAfter( 'Texture', texture, json.textures[ index ] ); + + } ); break; case 'skin': - dependency = this.loadSkin( index ); + dependency = this.loadSkin( index ).then( function ( skin ) { + + return parser._onAfter( 'Skin', skin, json.skins[ index ] ); + + } ); break; case 'animation': - dependency = this.loadAnimation( index ); + dependency = this.loadAnimation( index ).then( function ( animation ) { + + return parser._onAfter( 'Animation', animation, json.animations[ index ] ); + + } ); break; case 'camera': - dependency = this.loadCamera( index ); - break; + dependency = this.loadCamera( index ).then( function ( camera ) { + + return parser._onAfter( 'Camera', camera, json.cameras[ index ] ); - case 'light': - dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index ); + } ); break; default: @@ -1678,7 +1786,7 @@ THREE.GLTFLoader = ( function () { // 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 ); + return Promise.resolve( this.plugins.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); } @@ -1848,48 +1956,29 @@ THREE.GLTFLoader = ( function () { }; /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures - * @param {number} textureIndex + * @param {GLTF.Image} imageDef + * @param {THREE.Loader} loader (optional) * @return {Promise} */ - GLTFParser.prototype.loadTexture = function ( textureIndex ) { + GLTFParser.prototype.loadTextureFile = function ( imageDef, loader ) { var parser = this; - var json = this.json; var options = this.options; - var textureLoader = this.textureLoader; - - var URL = window.URL || window.webkitURL; - var textureDef = json.textures[ textureIndex ]; - - var textureExtensions = textureDef.extensions || {}; - - var source; - - if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) { - - source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ]; - - } else { - - source = json.images[ textureDef.source ]; - - } - - var sourceURI = source.uri; + var sourceURI = imageDef.uri; var isObjectURL = false; - if ( source.bufferView !== undefined ) { + if ( imageDef.bufferView !== undefined ) { // Load binary image data from bufferView, if provided. - sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) { + var URL = window.URL || window.webkitURL; + + sourceURI = parser.getDependency( 'bufferView', imageDef.bufferView ).then( function ( bufferView ) { isObjectURL = true; - var blob = new Blob( [ bufferView ], { type: source.mimeType } ); - sourceURI = URL.createObjectURL( blob ); - return sourceURI; + var blob = new Blob( [ bufferView ], { type: imageDef.mimeType } ); + return URL.createObjectURL( blob ); } ); @@ -1899,43 +1988,47 @@ THREE.GLTFLoader = ( function () { // Load Texture resource. - var loader = options.manager.getHandler( sourceURI ); + loader = options.manager.getHandler( sourceURI ) || loader || parser.textureLoader; + + return new Promise( function ( resolve, reject ) { - if ( ! loader ) { + loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); - loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] - ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader - : textureLoader; + } ).then( function ( texture ) { - } + // Clean up resources. + if ( isObjectURL ) URL.revokeObjectURL( sourceURI ); - return new Promise( function ( resolve, reject ) { + // Ignore unknown mime types, like DDS files. + if ( imageDef.mimeType in MIME_TYPE_FORMATS ) texture.format = MIME_TYPE_FORMATS[ imageDef.mimeType ]; - loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); + return texture; } ); - } ).then( function ( texture ) { + } ); - // Clean up resources and configure Texture. + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures + * @param {number} textureIndex + * @return {Promise} + */ + GLTFParser.prototype.loadTexture = function ( textureIndex ) { - if ( isObjectURL === true ) { + var json = this.json; + var textureDef = json.textures[ textureIndex ]; - URL.revokeObjectURL( sourceURI ); + var pending = this._on( 'Texture', textureDef ) || + this.loadTextureFile( json.images[ textureDef.source ] ); - } + return pending.then( function ( texture ) { texture.flipY = false; if ( textureDef.name !== undefined ) texture.name = textureDef.name; - // Ignore unknown mime types, like DDS files. - if ( source.mimeType in MIME_TYPE_FORMATS ) { - - texture.format = MIME_TYPE_FORMATS[ source.mimeType ]; - - } - var samplers = json.samplers || {}; var sampler = samplers[ textureDef.sampler ] || {}; @@ -1987,17 +2080,9 @@ THREE.GLTFLoader = ( function () { } - if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - - var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; - - if ( transform ) { - - texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); - - } + return parser._onAfter( 'Map', texture, mapDef ); - } + } ).then( function ( texture ) { materialParams[ mapName ] = texture; @@ -2017,7 +2102,7 @@ THREE.GLTFLoader = ( function () { var geometry = mesh.geometry; var material = mesh.material; - var extensions = this.extensions; + var extensions = this.plugins.extensions; var useVertexTangents = geometry.attributes.tangent !== undefined; var useVertexColors = geometry.attributes.color !== undefined; @@ -2071,7 +2156,7 @@ THREE.GLTFLoader = ( function () { var cacheKey = 'ClonedMaterial:' + material.uuid + ':'; - if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; + if ( material.isGLTFMeshStandardSGMaterial ) cacheKey += 'specular-glossiness:'; if ( useSkinning ) cacheKey += 'skinning:'; if ( useVertexTangents ) cacheKey += 'vertex-tangents:'; if ( useVertexColors ) cacheKey += 'vertex-colors:'; @@ -2083,9 +2168,7 @@ THREE.GLTFLoader = ( function () { if ( ! cachedMaterial ) { - cachedMaterial = material.isGLTFSpecularGlossinessMaterial - ? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material ) - : material.clone(); + cachedMaterial = material.clone(); if ( useSkinning ) cachedMaterial.skinning = true; if ( useVertexTangents ) cachedMaterial.vertexTangents = true; @@ -2110,13 +2193,6 @@ THREE.GLTFLoader = ( function () { } - if ( material.isGLTFSpecularGlossinessMaterial ) { - - // for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update - mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; - - } - // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 if ( material.normalScale && ! useVertexTangents ) { @@ -2137,34 +2213,19 @@ THREE.GLTFLoader = ( function () { var parser = this; var json = this.json; - var extensions = this.extensions; var materialDef = json.materials[ materialIndex ]; + var extensions = this.plugins.extensions; - var materialType; - var materialParams = {}; - var materialExtensions = materialDef.extensions || {}; + var materialPending = this._on( 'Material', materialDef ); - var pending = []; + if ( ! materialPending ) { - if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { - - var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; - materialType = sgExtension.getMaterialType(); - pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); - - } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - - var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; - materialType = kmuExtension.getMaterialType(); - pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); - - } else { + var materialParams = {}; + var pending = []; // Specification: // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material - materialType = THREE.MeshStandardMaterial; - var metallicRoughness = materialDef.pbrMetallicRoughness || {}; materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); @@ -2195,90 +2256,78 @@ THREE.GLTFLoader = ( function () { } - } - - if ( materialDef.doubleSided === true ) { + if ( materialDef.normalTexture !== undefined ) { - materialParams.side = THREE.DoubleSide; + pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); - } + materialParams.normalScale = new THREE.Vector2( 1, 1 ); - var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; + if ( materialDef.normalTexture.scale !== undefined ) { - if ( alphaMode === ALPHA_MODES.BLEND ) { - - materialParams.transparent = true; - - } else { + materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale ); - materialParams.transparent = false; - - if ( alphaMode === ALPHA_MODES.MASK ) { - - materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; + } } - } - - if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { + if ( materialDef.occlusionTexture !== undefined ) { - pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); + pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); - materialParams.normalScale = new THREE.Vector2( 1, 1 ); + if ( materialDef.occlusionTexture.strength !== undefined ) { - if ( materialDef.normalTexture.scale !== undefined ) { + materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; - materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale ); + } } - } + if ( materialDef.emissiveFactor !== undefined ) { - if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { + materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor ); - pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); + } - if ( materialDef.occlusionTexture.strength !== undefined ) { + if ( materialDef.emissiveTexture !== undefined ) { - materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; + pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) ); } - } + materialPending = Promise.all( pending ).then( function () { - if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial ) { + return new THREE.MeshStandardMaterial( materialParams ); - materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor ); + } ); } - if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { + return materialPending.then( function ( material ) { - pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) ); + if ( materialDef.name !== undefined ) material.name = materialDef.name; + if ( materialDef.doubleSided === true ) material.side = THREE.DoubleSide; - } + var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - return Promise.all( pending ).then( function () { + if ( alphaMode === ALPHA_MODES.BLEND ) { - var material; + material.transparent = true; - if ( materialType === THREE.ShaderMaterial ) { + } else { - material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); + material.transparent = false; - } else { + if ( alphaMode === ALPHA_MODES.MASK ) { - material = new materialType( materialParams ); + material.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; - } + } - if ( materialDef.name !== undefined ) material.name = materialDef.name; + } // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding. if ( material.map ) material.map.encoding = THREE.sRGBEncoding; if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding; - if ( material.specularMap ) material.specularMap.encoding = THREE.sRGBEncoding; assignExtrasToUserData( material, materialDef ); @@ -2545,21 +2594,9 @@ THREE.GLTFLoader = ( function () { GLTFParser.prototype.loadGeometries = function ( primitives ) { var parser = this; - var extensions = this.extensions; + var extensions = this.plugins.extensions; var cache = this.primitiveCache; - function createDracoPrimitive( primitive ) { - - return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] - .decodePrimitive( primitive, parser ) - .then( function ( geometry ) { - - return addPrimitiveAttributes( geometry, primitive, parser ); - - } ); - - } - var pending = []; for ( var i = 0, il = primitives.length; i < il; i ++ ) { @@ -2577,16 +2614,10 @@ THREE.GLTFLoader = ( function () { } else { - var geometryPromise; - - if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { - - // Use DRACO geometry if available - geometryPromise = createDracoPrimitive( primitive ); + var geometryPromise = this._on( 'Geometry', primitive ); - } else { + if ( ! geometryPromise ) { - // Otherwise create a new geometry geometryPromise = addPrimitiveAttributes( new THREE.BufferGeometry(), primitive, parser ); } @@ -3002,7 +3033,7 @@ THREE.GLTFLoader = ( function () { GLTFParser.prototype.loadNode = function ( nodeIndex ) { var json = this.json; - var extensions = this.extensions; + var extensions = this.plugins.extensions; var parser = this; var meshReferences = json.meshReferences; @@ -3014,6 +3045,14 @@ THREE.GLTFLoader = ( function () { var pending = []; + var extensionNode = parser._on( 'Node', nodeDef ); + + if ( extensionNode !== null ) { + + pending.push( extensionNode ); + + } + if ( nodeDef.mesh !== undefined ) { pending.push( parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { @@ -3027,16 +3066,6 @@ THREE.GLTFLoader = ( function () { node = mesh.clone(); node.name += '_instance_' + instanceNum; - // onBeforeRender copy for Specular-Glossiness - node.onBeforeRender = mesh.onBeforeRender; - - for ( var i = 0, il = node.children.length; i < il; i ++ ) { - - node.children[ i ].name += '_instance_' + instanceNum; - node.children[ i ].onBeforeRender = mesh.children[ i ].onBeforeRender; - - } - } else { node = mesh; @@ -3072,14 +3101,6 @@ THREE.GLTFLoader = ( function () { } - if ( nodeDef.extensions - && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] - && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) { - - pending.push( parser.getDependency( 'light', nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light ) ); - - } - return Promise.all( pending ); }() ).then( function ( objects ) { @@ -3268,7 +3289,7 @@ THREE.GLTFLoader = ( function () { return function loadScene( sceneIndex ) { var json = this.json; - var extensions = this.extensions; + var extensions = this.plugins.extensions; var sceneDef = this.json.scenes[ sceneIndex ]; var parser = this; diff --git a/examples/jsm/loaders/GLTFLoader.js b/examples/jsm/loaders/GLTFLoader.js index 5bc217fe88bcfa..d74b95de4dc2dd 100644 --- a/examples/jsm/loaders/GLTFLoader.js +++ b/examples/jsm/loaders/GLTFLoader.js @@ -56,17 +56,13 @@ import { RGBFormat, RepeatWrapping, Scene, - ShaderLib, - ShaderMaterial, Skeleton, SkinnedMesh, Sphere, SpotLight, - TangentSpaceNormalMap, TextureLoader, TriangleFanDrawMode, TriangleStripDrawMode, - UniformsUtils, Vector2, Vector3, VectorKeyframeTrack, @@ -80,8 +76,16 @@ var GLTFLoader = ( function () { Loader.call( this, manager ); - this.dracoLoader = null; - this.ddsLoader = null; + this.plugins = { + extensions: {}, + generics: [] + }; + + this.registerPlugin( new GLTFMaterialsPbrSpecularGlossinessExtension() ); + this.registerPlugin( new GLTFMaterialsUnlitExtension() ); + this.registerPlugin( new GLTFMeshQuantizationExtension() ); + this.registerPlugin( new GLTFLightsExtension() ); + this.registerPlugin( new GLTFTextureTransformExtension() ); } @@ -166,14 +170,36 @@ var GLTFLoader = ( function () { setDRACOLoader: function ( dracoLoader ) { - this.dracoLoader = dracoLoader; + this.registerPlugin( new GLTFDracoMeshCompressionExtension( dracoLoader ) ); + return this; + + }, + + registerPlugin: function ( plugin ) { + + var extensionName = plugin.extension; + + if ( extensionName ) { + + this.plugins.extensions[ extensionName ] = plugin; + + } else { + + if ( ! this.plugins.generics.includes( plugin ) ) { + + this.plugins.generics.push( plugin ); + + } + + } + return this; }, setDDSLoader: function ( ddsLoader ) { - this.ddsLoader = ddsLoader; + this.registerPlugin( new GLTFTextureDDSExtension( ddsLoader ) ); return this; }, @@ -195,7 +221,9 @@ var GLTFLoader = ( function () { try { - extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); + // TODO: Binary is in core spec, not extension. + // Maybe better to rename and to handle out of plugin system. + this.registerPlugin( new GLTFBinaryExtension( data ) ); } catch ( error ) { @@ -204,7 +232,7 @@ var GLTFLoader = ( function () { } - content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; + content = this.plugins.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; } else { @@ -230,43 +258,9 @@ var GLTFLoader = ( function () { var extensionName = json.extensionsUsed[ i ]; var extensionsRequired = json.extensionsRequired || []; - switch ( extensionName ) { - - case EXTENSIONS.KHR_LIGHTS_PUNCTUAL: - extensions[ extensionName ] = new GLTFLightsExtension( json ); - break; - - case EXTENSIONS.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); - break; - - case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: - extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); - break; - - case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); - break; - - case EXTENSIONS.MSFT_TEXTURE_DDS: - extensions[ extensionName ] = new GLTFTextureDDSExtension( this.ddsLoader ); - 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 && this.plugins.extensions[ extensionName ] === undefined ) { - if ( extensionsRequired.indexOf( extensionName ) >= 0 ) { - - console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); - - } + console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); } @@ -274,7 +268,7 @@ var GLTFLoader = ( function () { } - var parser = new GLTFParser( json, extensions, { + var parser = new GLTFParser( json, this.plugins, { path: path || this.resourcePath || '', crossOrigin: this.crossOrigin, @@ -353,77 +347,102 @@ var GLTFLoader = ( function () { } - this.name = EXTENSIONS.MSFT_TEXTURE_DDS; + this.extension = EXTENSIONS.MSFT_TEXTURE_DDS; this.ddsLoader = ddsLoader; } + GLTFTextureDDSExtension.prototype = { + + constructor: GLTFTextureDDSExtension, + + onTexture: function ( textureDef, parser ) { + + var extensions = textureDef.extensions; + var json = parser.json; + var imageDef = json.images[ extensions[ this.extension ].source ]; + return parser.loadTextureFile( imageDef, this.ddsLoader ); + + } + + }; + /** * Punctual Lights Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ - function GLTFLightsExtension( json ) { - - this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; + function GLTFLightsExtension() { - var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {}; - this.lightDefs = extension.lights || []; + this.extension = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; } - GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) { + GLTFLightsExtension.prototype = { - var lightDef = this.lightDefs[ lightIndex ]; - var lightNode; + constructor: GLTFLightsExtension, - var color = new Color( 0xffffff ); - if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); + onNode: function ( nodeDef, parser ) { - var range = lightDef.range !== undefined ? lightDef.range : 0; + var extensions = nodeDef.extensions || {}; + var json = parser.json; + var lightExtension = json.extensions[ this.extension ] || {}; + var lightDefs = lightExtension.lights || []; + var lightIndex = extensions[ this.extension ].light; + var lightDef = lightDefs[ lightIndex ]; - switch ( lightDef.type ) { + var lightNode; - case 'directional': - lightNode = new DirectionalLight( color ); - lightNode.target.position.set( 0, 0, - 1 ); - lightNode.add( lightNode.target ); - break; + var color = new Color( 0xffffff ); - case 'point': - lightNode = new PointLight( color ); - lightNode.distance = range; - break; + if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); - 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; + var range = lightDef.range !== undefined ? lightDef.range : 0; - default: - throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' ); + 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; - // 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 ); + 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; - lightNode.decay = 2; + default: + throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' ); + + } - if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; + // 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.name = lightDef.name || ( 'light_' + lightIndex ); + lightNode.decay = 2; - return Promise.resolve( lightNode ); + if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; + + lightNode.name = lightDef.name || ( 'light_' + lightIndex ); + + return Promise.resolve( lightNode ); + + } }; @@ -434,46 +453,51 @@ var GLTFLoader = ( function () { */ function GLTFMaterialsUnlitExtension() { - this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; + this.extension = EXTENSIONS.KHR_MATERIALS_UNLIT; } - GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () { + GLTFMaterialsUnlitExtension.prototype = { - return MeshBasicMaterial; + constructor: GLTFMaterialsUnlitExtension, - }; + onMaterial: function ( materialDef, parser ) { + + var materialParams = {}; + var pending = []; - GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) { + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; - var pending = []; + var metallicRoughness = materialDef.pbrMetallicRoughness; - materialParams.color = new Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; + if ( metallicRoughness ) { - var metallicRoughness = materialDef.pbrMetallicRoughness; + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - if ( metallicRoughness ) { + var array = metallicRoughness.baseColorFactor; - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; - var array = metallicRoughness.baseColorFactor; + } - materialParams.color.fromArray( array ); - materialParams.opacity = array[ 3 ]; + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); + + } } - if ( metallicRoughness.baseColorTexture !== undefined ) { + return Promise.all( pending ).then( function () { - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); + return new MeshBasicMaterial( materialParams ); - } + } ); } - return Promise.all( pending ); - }; /* BINARY EXTENSION */ @@ -483,7 +507,7 @@ var GLTFLoader = ( function () { function GLTFBinaryExtension( data ) { - this.name = EXTENSIONS.KHR_BINARY_GLTF; + this.extension = EXTENSIONS.KHR_BINARY_GLTF; this.content = null; this.body = null; @@ -547,7 +571,7 @@ var GLTFLoader = ( function () { * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression */ - function GLTFDracoMeshCompressionExtension( json, dracoLoader ) { + function GLTFDracoMeshCompressionExtension( dracoLoader ) { if ( ! dracoLoader ) { @@ -555,69 +579,78 @@ var GLTFLoader = ( function () { } - this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; - this.json = json; + this.extension = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; this.dracoLoader = dracoLoader; this.dracoLoader.preload(); } - GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { + GLTFDracoMeshCompressionExtension.prototype = { - var json = this.json; - var dracoLoader = this.dracoLoader; - var bufferViewIndex = primitive.extensions[ this.name ].bufferView; - var gltfAttributeMap = primitive.extensions[ this.name ].attributes; - var threeAttributeMap = {}; - var attributeNormalizedMap = {}; - var attributeTypeMap = {}; + constructor: GLTFDracoMeshCompressionExtension, - for ( var attributeName in gltfAttributeMap ) { + onGeometry: function ( primitive, parser ) { - var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + var json = parser.json; + var dracoLoader = this.dracoLoader; + var bufferViewIndex = primitive.extensions[ this.extension ].bufferView; + var gltfAttributeMap = primitive.extensions[ this.extension ].attributes; + var threeAttributeMap = {}; + var attributeNormalizedMap = {}; + var attributeTypeMap = {}; - threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; + for ( var attributeName in gltfAttributeMap ) { - } + var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; + + } - for ( attributeName in primitive.attributes ) { + for ( attributeName in primitive.attributes ) { - var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); - if ( gltfAttributeMap[ attributeName ] !== undefined ) { + if ( gltfAttributeMap[ attributeName ] !== undefined ) { - var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; + var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; - attributeTypeMap[ threeAttributeName ] = componentType; - attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; + attributeTypeMap[ threeAttributeName ] = componentType; + attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; + + } } - } + return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { - return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { + return new Promise( function ( resolve ) { - return new Promise( function ( resolve ) { + dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { - dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { + for ( var attributeName in geometry.attributes ) { - for ( var attributeName in geometry.attributes ) { + var attribute = geometry.attributes[ attributeName ]; + var normalized = attributeNormalizedMap[ attributeName ]; - var attribute = geometry.attributes[ attributeName ]; - var normalized = attributeNormalizedMap[ attributeName ]; + if ( normalized !== undefined ) attribute.normalized = normalized; - if ( normalized !== undefined ) attribute.normalized = normalized; + } - } + resolve( geometry ); + + }, threeAttributeMap, attributeTypeMap ); - resolve( geometry ); + } ).then( function ( geometry ) { - }, threeAttributeMap, attributeTypeMap ); + return addPrimitiveAttributes( geometry, primitive, parser ); + + } ); } ); - } ); + }, }; @@ -628,438 +661,305 @@ var GLTFLoader = ( function () { */ function GLTFTextureTransformExtension() { - this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; + this.extension = EXTENSIONS.KHR_TEXTURE_TRANSFORM; } - GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) { - - texture = texture.clone(); - - 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 ); - - } - - if ( transform.texCoord !== undefined ) { - - console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); + GLTFTextureTransformExtension.prototype = { - } + constructor: GLTFTextureTransformExtension, - texture.needsUpdate = true; + onAfterMap: function ( texture, textureDef, parser ) { - return texture; + var extensions = textureDef.extensions; + var transform = extensions[ this.name ]; - }; + texture = texture.clone(); - /** - * Specular-Glossiness Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness - */ - function GLTFMaterialsPbrSpecularGlossinessExtension() { + if ( transform.offset !== undefined ) { - return { - - name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, - - specularGlossinessParams: [ - 'color', - 'map', - 'lightMap', - 'lightMapIntensity', - 'aoMap', - 'aoMapIntensity', - 'emissive', - 'emissiveIntensity', - 'emissiveMap', - 'bumpMap', - 'bumpScale', - 'normalMap', - 'normalMapType', - 'displacementMap', - 'displacementScale', - 'displacementBias', - 'specularMap', - 'specular', - 'glossinessMap', - 'glossiness', - 'alphaMap', - 'envMap', - 'envMapIntensity', - 'refractionRatio', - ], - - getMaterialType: function () { - - return ShaderMaterial; + texture.offset.fromArray( transform.offset ); - }, + } - extendParams: function ( materialParams, materialDef, parser ) { - - var pbrSpecularGlossiness = materialDef.extensions[ this.name ]; - - var shader = ShaderLib[ 'standard' ]; - - var uniforms = UniformsUtils.clone( shader.uniforms ); - - var specularMapParsFragmentChunk = [ - '#ifdef USE_SPECULARMAP', - ' uniform sampler2D specularMap;', - '#endif' - ].join( '\n' ); - - var glossinessMapParsFragmentChunk = [ - '#ifdef USE_GLOSSINESSMAP', - ' uniform sampler2D glossinessMap;', - '#endif' - ].join( '\n' ); - - var specularMapFragmentChunk = [ - 'vec3 specularFactor = specular;', - '#ifdef USE_SPECULARMAP', - ' vec4 texelSpecular = texture2D( specularMap, vUv );', - ' texelSpecular = sRGBToLinear( texelSpecular );', - ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', - ' specularFactor *= texelSpecular.rgb;', - '#endif' - ].join( '\n' ); - - var glossinessMapFragmentChunk = [ - 'float glossinessFactor = glossiness;', - '#ifdef USE_GLOSSINESSMAP', - ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', - ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', - ' glossinessFactor *= texelGlossiness.a;', - '#endif' - ].join( '\n' ); - - var lightPhysicalFragmentChunk = [ - 'PhysicalMaterial material;', - 'material.diffuseColor = diffuseColor.rgb;', - 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );', - 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );', - 'material.specularRoughness = max( 1.0 - glossinessFactor, 0.0525 );// 0.0525 corresponds to the base mip of a 256 cubemap.', - 'material.specularRoughness += geometryRoughness;', - 'material.specularRoughness = min( material.specularRoughness, 1.0 );', - 'material.specularColor = specularFactor.rgb;', - ].join( '\n' ); - - var fragmentShader = shader.fragmentShader - .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) - .replace( 'uniform float metalness;', 'uniform float glossiness;' ) - .replace( '#include ', specularMapParsFragmentChunk ) - .replace( '#include ', glossinessMapParsFragmentChunk ) - .replace( '#include ', specularMapFragmentChunk ) - .replace( '#include ', glossinessMapFragmentChunk ) - .replace( '#include ', lightPhysicalFragmentChunk ); - - delete uniforms.roughness; - delete uniforms.metalness; - delete uniforms.roughnessMap; - delete uniforms.metalnessMap; - - uniforms.specular = { value: new Color().setHex( 0x111111 ) }; - uniforms.glossiness = { value: 0.5 }; - uniforms.specularMap = { value: null }; - uniforms.glossinessMap = { value: null }; - - materialParams.vertexShader = shader.vertexShader; - materialParams.fragmentShader = fragmentShader; - materialParams.uniforms = uniforms; - materialParams.defines = { 'STANDARD': '' }; - - materialParams.color = new Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; + if ( transform.rotation !== undefined ) { - var pending = []; + texture.rotation = transform.rotation; - if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + } - var array = pbrSpecularGlossiness.diffuseFactor; + if ( transform.scale !== undefined ) { - materialParams.color.fromArray( array ); - materialParams.opacity = array[ 3 ]; + texture.repeat.fromArray( transform.scale ); - } + } - if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + if ( transform.texCoord !== undefined ) { - pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); + console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); - } + } - materialParams.emissive = new Color( 0.0, 0.0, 0.0 ); - materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; - materialParams.specular = new Color( 1.0, 1.0, 1.0 ); + texture.needsUpdate = true; - if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { + return texture; - materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + } - } + }; - if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + function GLTFMeshStandardSGMaterial( params ) { + + MeshStandardMaterial.call( this ); + + var specularMapParsFragmentChunk = [ + '#ifdef USE_SPECULARMAP', + ' uniform sampler2D specularMap;', + '#endif' + ].join( '\n' ); + + var glossinessMapParsFragmentChunk = [ + '#ifdef USE_GLOSSINESSMAP', + ' uniform sampler2D glossinessMap;', + '#endif' + ].join( '\n' ); + + var specularMapFragmentChunk = [ + 'vec3 specularFactor = specular;', + '#ifdef USE_SPECULARMAP', + ' vec4 texelSpecular = texture2D( specularMap, vUv );', + ' texelSpecular = sRGBToLinear( texelSpecular );', + ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' specularFactor *= texelSpecular.rgb;', + '#endif' + ].join( '\n' ); + + var glossinessMapFragmentChunk = [ + 'float glossinessFactor = glossiness;', + '#ifdef USE_GLOSSINESSMAP', + ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', + ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' glossinessFactor *= texelGlossiness.a;', + '#endif' + ].join( '\n' ); + + var lightPhysicalFragmentChunk = [ + 'PhysicalMaterial material;', + 'material.diffuseColor = diffuseColor.rgb;', + 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', + 'material.specularColor = specularFactor.rgb;', + ].join( '\n' ); + + var uniforms = { + specular: { value: new Color().setHex( 0xffffff ) }, + glossiness: { value: 1 }, + specularMap: { value: null }, + glossinessMap: { value: null } + }; - var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; - pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); - pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); + this._extraUniforms = uniforms; - } + this.onBeforeCompile = function ( shader ) { - return Promise.all( pending ); + for ( var uniformName in uniforms ) { - }, + shader.uniforms[ uniformName ] = uniforms[ uniformName ]; - createMaterial: function ( params ) { + } - // setup material properties based on MeshStandardMaterial for Specular-Glossiness + shader.fragmentShader = shader.fragmentShader.replace( 'uniform float roughness;', 'uniform vec3 specular;' ); + shader.fragmentShader = shader.fragmentShader.replace( 'uniform float metalness;', 'uniform float glossiness;' ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapParsFragmentChunk ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapParsFragmentChunk ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', specularMapFragmentChunk ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', glossinessMapFragmentChunk ); + shader.fragmentShader = shader.fragmentShader.replace( '#include ', lightPhysicalFragmentChunk ); - var material = new ShaderMaterial( { - defines: params.defines, - vertexShader: params.vertexShader, - fragmentShader: params.fragmentShader, - uniforms: params.uniforms, - fog: true, - lights: true, - opacity: params.opacity, - transparent: params.transparent - } ); + }; - material.isGLTFSpecularGlossinessMaterial = true; + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; - material.color = params.color; + this.setValues( params ); - material.map = params.map === undefined ? null : params.map; + } - material.lightMap = null; - material.lightMapIntensity = 1.0; + GLTFMeshStandardSGMaterial.prototype = Object.assign( Object.create( MeshStandardMaterial.prototype ), { - material.aoMap = params.aoMap === undefined ? null : params.aoMap; - material.aoMapIntensity = 1.0; + constructor: GLTFMeshStandardSGMaterial, - material.emissive = params.emissive; - material.emissiveIntensity = 1.0; - material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap; + isGLTFMeshStandardSGMaterial: true, - material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap; - material.bumpScale = 1; + copy: function ( source ) { - material.normalMap = params.normalMap === undefined ? null : params.normalMap; - material.normalMapType = TangentSpaceNormalMap; + MeshStandardMaterial.prototype.copy.call( this, source ); - if ( params.normalScale ) material.normalScale = params.normalScale; + this.specularMap = source.specularMap; + this.specular.copy( source.specular ); + this.glossinessMap = source.glossinessMap; + this.glossiness = source.glossiness; - material.displacementMap = null; - material.displacementScale = 1; - material.displacementBias = 0; + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; - material.specularMap = params.specularMap === undefined ? null : params.specularMap; - material.specular = params.specular; + return this; - material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap; - material.glossiness = params.glossiness; + } - material.alphaMap = null; + } ); - material.envMap = params.envMap === undefined ? null : params.envMap; - material.envMapIntensity = 1.0; + Object.defineProperties( GLTFMeshStandardSGMaterial.prototype, { - material.refractionRatio = 0.98; + specular: { - material.extensions.derivatives = true; + get: function () { - return material; + return this._extraUniforms.specular.value; }, - /** - * Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can - * copy only properties it knows about or inherits, and misses many properties that would - * normally be defined by MeshStandardMaterial. - * - * This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of - * loading a glTF model, but cloning later (e.g. by the user) would require these changes - * AND also updating `.onBeforeRender` on the parent mesh. - * - * @param {ShaderMaterial} source - * @return {ShaderMaterial} - */ - cloneMaterial: function ( source ) { + set: function ( v ) { - var target = source.clone(); + this._extraUniforms.specular.value = v; - target.isGLTFSpecularGlossinessMaterial = true; - - var params = this.specularGlossinessParams; + } - for ( var i = 0, il = params.length; i < il; i ++ ) { + }, - var value = source[ params[ i ] ]; - target[ params[ i ] ] = ( value && value.isColor ) ? value.clone() : value; + glossiness: { - } + get: function () { - return target; + return this._extraUniforms.glossiness.value; }, - // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. - refreshUniforms: function ( renderer, scene, camera, geometry, material ) { - - if ( material.isGLTFSpecularGlossinessMaterial !== true ) { - - return; + set: function ( v ) { - } - - var uniforms = material.uniforms; - var defines = material.defines; - - uniforms.opacity.value = material.opacity; - - uniforms.diffuse.value.copy( material.color ); - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); + this._extraUniforms.glossiness.value = v; - uniforms.map.value = material.map; - uniforms.specularMap.value = material.specularMap; - uniforms.alphaMap.value = material.alphaMap; + } - uniforms.lightMap.value = material.lightMap; - uniforms.lightMapIntensity.value = material.lightMapIntensity; + }, - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; + glossinessMap: { - // uv repeat and offset setting priorities - // 1. color map - // 2. specular map - // 3. normal map - // 4. bump map - // 5. alpha map - // 6. emissive map + get: function () { - var uvScaleMap; + this._extraUniforms.glossinessMap.value; - if ( material.map ) { + }, - uvScaleMap = material.map; + set: function ( v ) { - } else if ( material.specularMap ) { + this._extraUniforms.glossinessMap.value = v; - uvScaleMap = material.specularMap; + if ( v ) { - } else if ( material.displacementMap ) { + this.defines.USE_GLOSSINESSMAP = ''; + // set USE_ROUGHNESSMAP to enable vUv + this.defines.USE_ROUGHNESSMAP = ''; - uvScaleMap = material.displacementMap; + } else { - } else if ( material.normalMap ) { + delete this.defines.USE_ROUGHNESSMAP; + delete this.defines.USE_GLOSSINESSMAP; - uvScaleMap = material.normalMap; + } - } else if ( material.bumpMap ) { + } - uvScaleMap = material.bumpMap; + } - } else if ( material.glossinessMap ) { + } ); - uvScaleMap = material.glossinessMap; + /** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness + */ + function GLTFMaterialsPbrSpecularGlossinessExtension() { - } else if ( material.alphaMap ) { + this.extension = EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS; - uvScaleMap = material.alphaMap; + } - } else if ( material.emissiveMap ) { + GLTFMaterialsPbrSpecularGlossinessExtension.prototype = { - uvScaleMap = material.emissiveMap; + constructor: GLTFMaterialsPbrSpecularGlossinessExtension, - } + onAfterMaterial: function( material, materialDef, parser ) { - if ( uvScaleMap !== undefined ) { + // Create Specular-Glossiness material from MeshStandardMaterial instance. + // Rewrite to onMaterial if you want to avoid MeshStandardMaterial creation cost. - // backwards compatibility - if ( uvScaleMap.isWebGLRenderTarget ) { + var extensions = materialDef.extensions; + var pbrSpecularGlossiness = extensions[ this.extension ]; - uvScaleMap = uvScaleMap.texture; + var newMaterial = new GLTFMeshStandardSGMaterial(); - } + MeshStandardMaterial.prototype.copy.call( newMaterial, material ); - if ( uvScaleMap.matrixAutoUpdate === true ) { + delete newMaterial.metalness; + delete newMaterial.roughness; + delete newMaterial.metalnessMap; + delete newMaterial.roughnessMap; - uvScaleMap.updateMatrix(); + var pending = []; - } + newMaterial.color.setRGB( 1.0, 1.0, 1.0 ); + newMaterial.opacity = 1.0; - uniforms.uvTransform.value.copy( uvScaleMap.matrix ); + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { - } + var array = pbrSpecularGlossiness.diffuseFactor; - if ( material.envMap ) { + newMaterial.color.fromArray( array ); + newMaterial.opacity = array[ 3 ]; - uniforms.envMap.value = material.envMap; - uniforms.envMapIntensity.value = material.envMapIntensity; + } - // don't flip CubeTexture envMaps, flip everything else: - // WebGLCubeRenderTarget will be flipped for backwards compatibility - // WebGLCubeRenderTarget.texture will be flipped because it's a Texture and NOT a CubeTexture - // this check must be handled differently, or removed entirely, if WebGLCubeRenderTarget uses a CubeTexture in the future - uniforms.flipEnvMap.value = material.envMap.isCubeTexture ? - 1 : 1; + newMaterial.map = null; - uniforms.reflectivity.value = material.reflectivity; - uniforms.refractionRatio.value = material.refractionRatio; + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { - uniforms.maxMipLevel.value = renderer.properties.get( material.envMap ).__maxMipLevel; + pending.push( parser.assignTexture( newMaterial, 'map', pbrSpecularGlossiness.diffuseTexture ) ); - } + } - uniforms.specular.value.copy( material.specular ); - uniforms.glossiness.value = material.glossiness; + newMaterial.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; + newMaterial.specular.setRGB( 1.0, 1.0, 1.0 ); - uniforms.glossinessMap.value = material.glossinessMap; + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { - uniforms.emissiveMap.value = material.emissiveMap; - uniforms.bumpMap.value = material.bumpMap; - uniforms.normalMap.value = material.normalMap; + newMaterial.specular.fromArray( pbrSpecularGlossiness.specularFactor ); - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; + } - if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { - defines.USE_GLOSSINESSMAP = ''; - // set USE_ROUGHNESSMAP to enable vUv - defines.USE_ROUGHNESSMAP = ''; + var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; + pending.push( parser.assignTexture( newMaterial, 'glossinessMap', specGlossMapDef ) ); + pending.push( parser.assignTexture( newMaterial, 'specularMap', specGlossMapDef ) ); - } + } - if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { + return Promise.all( pending ).then( function () { - delete defines.USE_GLOSSINESSMAP; - delete defines.USE_ROUGHNESSMAP; + if ( newMaterial.map ) newMaterial.map.encoding = sRGBEncoding; + if ( newMaterial.specularMap ) newMaterial.specularMap.encoding = sRGBEncoding; - } + return newMaterial; - } + } ); - }; + } - } + }; /** * Mesh Quantization Extension @@ -1068,7 +968,7 @@ var GLTFLoader = ( function () { */ function GLTFMeshQuantizationExtension() { - this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; + this.extension = EXTENSIONS.KHR_MESH_QUANTIZATION; } @@ -1462,6 +1362,7 @@ var GLTFLoader = ( function () { if ( dracoExtension ) { + // TODO: Can we move this under GLTFDracoMeshCompressionExtension? geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices + ':' + createAttributesKey( dracoExtension.attributes ); @@ -1494,10 +1395,10 @@ var GLTFLoader = ( function () { /* GLTF PARSER */ - function GLTFParser( json, extensions, options ) { + function GLTFParser( json, plugins, options ) { this.json = json || {}; - this.extensions = extensions || {}; + this.plugins = plugins || { extensions: {}, generics: [] }; this.options = options || {}; // loader object cache @@ -1524,7 +1425,7 @@ var GLTFLoader = ( function () { var parser = this; var json = this.json; - var extensions = this.extensions; + var extensions = this.plugins.extensions; // Clear the loader cache this.cache.removeAll(); @@ -1532,13 +1433,19 @@ var GLTFLoader = ( function () { // Mark the special nodes/meshes in json for efficient parse this.markDefs(); - Promise.all( [ + this._onBefore( 'GLTF', json ).then( function ( json ) { - this.getDependencies( 'scene' ), - this.getDependencies( 'animation' ), - this.getDependencies( 'camera' ), + parser.json = json; - ] ).then( function ( dependencies ) { + return Promise.all( [ + + parser.getDependencies( 'scene' ), + parser.getDependencies( 'animation' ), + parser.getDependencies( 'camera' ), + + ] ); + + } ).then( function ( dependencies ) { var result = { scene: dependencies[ 0 ][ json.scene || 0 ], @@ -1554,7 +1461,7 @@ var GLTFLoader = ( function () { assignExtrasToUserData( result, json ); - onLoad( result ); + parser._onAfter( 'GLTF', result, json ).then( onLoad ); } ).catch( onError ); @@ -1623,6 +1530,161 @@ var GLTFLoader = ( function () { }; + /** + * @param {string} key + * @param {GLTF.definition} def + * @return {Promise}} + */ + GLTFParser.prototype._onBefore = function ( key, def ) { + + var parser = this; + var functionName = 'onBefore' + key; + var plugins = this.plugins || {}; + var extensionPlugins = plugins.extensions || {}; + var genericPlugins = plugins.generics || {}; + var extensions = def.extensions || {}; + var pending = Promise.resolve( def ); + + for ( var extensionName in extensions ) { + + var plugin = extensionPlugins[ extensionName ]; + + if ( plugin === undefined || plugin[ functionName ] === undefined ) { + + continue; + + } + + pending = pending.then( function ( plugin, def ) { + + return plugin[ functionName ]( def, parser ); + + }.bind( null, plugin ) ); + + } + + for ( var plugin of genericPlugins ) { + + if ( plugin[ functionName ] === undefined ) { + + continue; + + } + + pending = pending.then( function ( plugin, def ) { + + return plugin[ functionName ]( def, parser ); + + }.bind( null, plugin ) ); + + } + + return pending; + + }; + + /** + * @param {string} key + * @param {Object} object + * @param {GLTF.definition} def + * @return {Promise}} + */ + GLTFParser.prototype._onAfter = function ( key, object, def ) { + + var parser = this; + var functionName = 'onAfter' + key; + var plugins = this.plugins || {}; + var extensionPlugins = plugins.extensions || {}; + var genericPlugins = plugins.generics || []; + var extensions = def.extensions || {}; + var pending = Promise.resolve( object ); + + for ( var extensionName in extensions ) { + + var plugin = extensionPlugins[ extensionName ]; + + if ( plugin === undefined || plugin[ functionName ] === undefined ) { + + continue; + + } + + pending = pending.then( function ( plugin, object ) { + + return plugin[ functionName ]( object, def, parser ); + + }.bind( null, plugin ) ); + + } + + for ( var plugin of genericPlugins ) { + + if ( plugin[ functionName ] === undefined ) { + + continue; + + } + + pending = pending.then( function ( plugin, object ) { + + return plugin[ functionName ]( object, def, parser ); + + }.bind( null, plugin ) ); + + } + + return pending; + + }; + + /** + * @param {string} key + * @param {GLTF.definition} def + * @return {Promise|null}} + */ + GLTFParser.prototype._on = function ( key, def ) { + + var parser = this; + var functionName = 'on' + key; + var plugins = this.plugins || {}; + var extensionPlugins = plugins.extensions || {}; + var genericPlugins = plugins.generics || []; + var extensions = def.extensions || {}; + + for ( var extensionName in extensions ) { + + var plugin = extensionPlugins[ extensionName ]; + + if ( plugin === undefined || plugin[ functionName ] === undefined ) { + + continue; + + } + + var result = plugin[ functionName ]( def, parser ); + + if ( result ) return result; + + } + + for ( var plugin of genericPlugins ) { + + if ( plugin[ functionName ] === undefined ) { + + continue; + + } + + var result = plugin[ functionName ]( def, parser ); + + if ( result ) return result; + + } + + return null; + + }; + /** * Requests the specified dependency asynchronously, with caching. * @param {string} type @@ -1631,6 +1693,8 @@ var GLTFLoader = ( function () { */ GLTFParser.prototype.getDependency = function ( type, index ) { + var parser = this; + var json = parser.json; var cacheKey = type + ':' + index; var dependency = this.cache.get( cacheKey ); @@ -1639,51 +1703,91 @@ var GLTFLoader = ( function () { switch ( type ) { case 'scene': - dependency = this.loadScene( index ); + dependency = this.loadScene( index ).then( function ( scene ) { + + return parser._onAfter( 'Scene', scene, json.scenes[ index ] ); + + } ); break; case 'node': - dependency = this.loadNode( index ); + dependency = this.loadNode( index ).then( function ( node ) { + + return parser._onAfter( 'Node', node, json.nodes[ index ] ); + + } ); break; case 'mesh': - dependency = this.loadMesh( index ); + dependency = this.loadMesh( index ).then( function ( mesh ) { + + return parser._onAfter( 'Mesh', mesh, json.meshes[ index ] ); + + } ); break; case 'accessor': - dependency = this.loadAccessor( index ); + dependency = this.loadAccessor( index ).then( function ( accessor ) { + + return parser._onAfter( 'Accessor', accessor, json.accessors[ index ] ); + + } ); break; case 'bufferView': - dependency = this.loadBufferView( index ); + dependency = this.loadBufferView( index ).then( function ( bufferView ) { + + return parser._onAfter( 'BufferView', bufferView, json.bufferViews[ index ] ); + + } ); break; case 'buffer': - dependency = this.loadBuffer( index ); + dependency = this.loadBuffer( index ).then( function ( buffer ) { + + return parser._onAfter( 'Buffer', buffer, json.buffers[ index ] ); + + } ); break; case 'material': - dependency = this.loadMaterial( index ); + dependency = this.loadMaterial( index ).then( function ( material ) { + + return parser._onAfter( 'Material', material, json.materials[ index ] ); + + } ); break; case 'texture': - dependency = this.loadTexture( index ); + dependency = this.loadTexture( index ).then( function ( texture ) { + + return parser._onAfter( 'Texture', texture, json.textures[ index ] ); + + } ); break; case 'skin': - dependency = this.loadSkin( index ); + dependency = this.loadSkin( index ).then( function ( skin ) { + + return parser._onAfter( 'Skin', skin, json.skins[ index ] ); + + } ); break; case 'animation': - dependency = this.loadAnimation( index ); + dependency = this.loadAnimation( index ).then( function ( animation ) { + + return parser._onAfter( 'Animation', animation, json.animations[ index ] ); + + } ); break; case 'camera': - dependency = this.loadCamera( index ); - break; + dependency = this.loadCamera( index ).then( function ( camera ) { + + return parser._onAfter( 'Camera', camera, json.cameras[ index ] ); - case 'light': - dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index ); + } ); break; default: @@ -1746,7 +1850,7 @@ var GLTFLoader = ( function () { // 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 ); + return Promise.resolve( this.plugins.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); } @@ -1916,48 +2020,29 @@ var GLTFLoader = ( function () { }; /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures - * @param {number} textureIndex + * @param {GLTF.Image} imageDef + * @param {Loader} loader (optional) * @return {Promise} */ - GLTFParser.prototype.loadTexture = function ( textureIndex ) { + GLTFParser.prototype.loadTextureFile = function ( imageDef, loader ) { var parser = this; - var json = this.json; var options = this.options; - var textureLoader = this.textureLoader; - - var URL = window.URL || window.webkitURL; - var textureDef = json.textures[ textureIndex ]; - - var textureExtensions = textureDef.extensions || {}; - - var source; - - if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) { - - source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ]; - - } else { - - source = json.images[ textureDef.source ]; - - } - - var sourceURI = source.uri; + var sourceURI = imageDef.uri; var isObjectURL = false; - if ( source.bufferView !== undefined ) { + if ( imageDef.bufferView !== undefined ) { // Load binary image data from bufferView, if provided. - sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) { + var URL = window.URL || window.webkitURL; + + sourceURI = parser.getDependency( 'bufferView', imageDef.bufferView ).then( function ( bufferView ) { isObjectURL = true; - var blob = new Blob( [ bufferView ], { type: source.mimeType } ); - sourceURI = URL.createObjectURL( blob ); - return sourceURI; + var blob = new Blob( [ bufferView ], { type: imageDef.mimeType } ); + return URL.createObjectURL( blob ); } ); @@ -1967,43 +2052,47 @@ var GLTFLoader = ( function () { // Load Texture resource. - var loader = options.manager.getHandler( sourceURI ); + loader = options.manager.getHandler( sourceURI ) || loader || parser.textureLoader; + + return new Promise( function ( resolve, reject ) { - if ( ! loader ) { + loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); - loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] - ? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader - : textureLoader; + } ).then( function ( texture ) { - } + // Clean up resources. + if ( isObjectURL ) URL.revokeObjectURL( sourceURI ); - return new Promise( function ( resolve, reject ) { + // Ignore unknown mime types, like DDS files. + if ( imageDef.mimeType in MIME_TYPE_FORMATS ) texture.format = MIME_TYPE_FORMATS[ imageDef.mimeType ]; - loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); + return texture; } ); - } ).then( function ( texture ) { + } ); - // Clean up resources and configure Texture. + }; + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures + * @param {number} textureIndex + * @return {Promise} + */ + GLTFParser.prototype.loadTexture = function ( textureIndex ) { - if ( isObjectURL === true ) { + var json = this.json; + var textureDef = json.textures[ textureIndex ]; - URL.revokeObjectURL( sourceURI ); + var pending = this._on( 'Texture', textureDef ) || + this.loadTextureFile( json.images[ textureDef.source ] ); - } + return pending.then( function ( texture ) { texture.flipY = false; if ( textureDef.name !== undefined ) texture.name = textureDef.name; - // Ignore unknown mime types, like DDS files. - if ( source.mimeType in MIME_TYPE_FORMATS ) { - - texture.format = MIME_TYPE_FORMATS[ source.mimeType ]; - - } - var samplers = json.samplers || {}; var sampler = samplers[ textureDef.sampler ] || {}; @@ -2055,17 +2144,9 @@ var GLTFLoader = ( function () { } - if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - - var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; - - if ( transform ) { - - texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); - - } + return parser._onAfter( 'Map', texture, mapDef ); - } + } ).then( function ( texture ) { materialParams[ mapName ] = texture; @@ -2085,7 +2166,7 @@ var GLTFLoader = ( function () { var geometry = mesh.geometry; var material = mesh.material; - var extensions = this.extensions; + var extensions = this.plugins.extensions; var useVertexTangents = geometry.attributes.tangent !== undefined; var useVertexColors = geometry.attributes.color !== undefined; @@ -2139,7 +2220,7 @@ var GLTFLoader = ( function () { var cacheKey = 'ClonedMaterial:' + material.uuid + ':'; - if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; + if ( material.isGLTFMeshStandardSGMaterial ) cacheKey += 'specular-glossiness:'; if ( useSkinning ) cacheKey += 'skinning:'; if ( useVertexTangents ) cacheKey += 'vertex-tangents:'; if ( useVertexColors ) cacheKey += 'vertex-colors:'; @@ -2151,9 +2232,7 @@ var GLTFLoader = ( function () { if ( ! cachedMaterial ) { - cachedMaterial = material.isGLTFSpecularGlossinessMaterial - ? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material ) - : material.clone(); + cachedMaterial = material.clone(); if ( useSkinning ) cachedMaterial.skinning = true; if ( useVertexTangents ) cachedMaterial.vertexTangents = true; @@ -2178,13 +2257,6 @@ var GLTFLoader = ( function () { } - if ( material.isGLTFSpecularGlossinessMaterial ) { - - // for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update - mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; - - } - // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 if ( material.normalScale && ! useVertexTangents ) { @@ -2205,34 +2277,19 @@ var GLTFLoader = ( function () { var parser = this; var json = this.json; - var extensions = this.extensions; var materialDef = json.materials[ materialIndex ]; + var extensions = this.plugins.extensions; - var materialType; - var materialParams = {}; - var materialExtensions = materialDef.extensions || {}; + var materialPending = this._on( 'Material', materialDef ); - var pending = []; + if ( ! materialPending ) { - if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { - - var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; - materialType = sgExtension.getMaterialType(); - pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); - - } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { - - var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; - materialType = kmuExtension.getMaterialType(); - pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); - - } else { + var materialParams = {}; + var pending = []; // Specification: // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material - materialType = MeshStandardMaterial; - var metallicRoughness = materialDef.pbrMetallicRoughness || {}; materialParams.color = new Color( 1.0, 1.0, 1.0 ); @@ -2263,90 +2320,78 @@ var GLTFLoader = ( function () { } - } - - if ( materialDef.doubleSided === true ) { + if ( materialDef.normalTexture !== undefined ) { - materialParams.side = DoubleSide; + pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); - } + materialParams.normalScale = new Vector2( 1, 1 ); - var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; + if ( materialDef.normalTexture.scale !== undefined ) { - if ( alphaMode === ALPHA_MODES.BLEND ) { - - materialParams.transparent = true; - - } else { + materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale ); - materialParams.transparent = false; - - if ( alphaMode === ALPHA_MODES.MASK ) { - - materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; + } } - } - - if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) { + if ( materialDef.occlusionTexture !== undefined ) { - pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); + pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); - materialParams.normalScale = new Vector2( 1, 1 ); + if ( materialDef.occlusionTexture.strength !== undefined ) { - if ( materialDef.normalTexture.scale !== undefined ) { + materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; - materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale ); + } } - } + if ( materialDef.emissiveFactor !== undefined ) { - if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) { + materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor ); - pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); + } - if ( materialDef.occlusionTexture.strength !== undefined ) { + if ( materialDef.emissiveTexture !== undefined ) { - materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; + pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) ); } - } + materialPending = Promise.all( pending ).then( function () { - if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) { + return new MeshStandardMaterial( materialParams ); - materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor ); + } ); } - if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) { + return materialPending.then( function ( material ) { - pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) ); + if ( materialDef.name !== undefined ) material.name = materialDef.name; + if ( materialDef.doubleSided === true ) material.side = DoubleSide; - } + var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; - return Promise.all( pending ).then( function () { + if ( alphaMode === ALPHA_MODES.BLEND ) { - var material; + material.transparent = true; - if ( materialType === ShaderMaterial ) { + } else { - material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); + material.transparent = false; - } else { + if ( alphaMode === ALPHA_MODES.MASK ) { - material = new materialType( materialParams ); + material.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; - } + } - if ( materialDef.name !== undefined ) material.name = materialDef.name; + } // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding. if ( material.map ) material.map.encoding = sRGBEncoding; if ( material.emissiveMap ) material.emissiveMap.encoding = sRGBEncoding; - if ( material.specularMap ) material.specularMap.encoding = sRGBEncoding; assignExtrasToUserData( material, materialDef ); @@ -2613,21 +2658,9 @@ var GLTFLoader = ( function () { GLTFParser.prototype.loadGeometries = function ( primitives ) { var parser = this; - var extensions = this.extensions; + var extensions = this.plugins.extensions; var cache = this.primitiveCache; - function createDracoPrimitive( primitive ) { - - return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] - .decodePrimitive( primitive, parser ) - .then( function ( geometry ) { - - return addPrimitiveAttributes( geometry, primitive, parser ); - - } ); - - } - var pending = []; for ( var i = 0, il = primitives.length; i < il; i ++ ) { @@ -2645,16 +2678,10 @@ var GLTFLoader = ( function () { } else { - var geometryPromise; - - if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { - - // Use DRACO geometry if available - geometryPromise = createDracoPrimitive( primitive ); + var geometryPromise = this._on( 'Geometry', primitive ); - } else { + if ( ! geometryPromise ) { - // Otherwise create a new geometry geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser ); } @@ -3070,7 +3097,7 @@ var GLTFLoader = ( function () { GLTFParser.prototype.loadNode = function ( nodeIndex ) { var json = this.json; - var extensions = this.extensions; + var extensions = this.plugins.extensions; var parser = this; var meshReferences = json.meshReferences; @@ -3082,6 +3109,14 @@ var GLTFLoader = ( function () { var pending = []; + var extensionNode = parser._on( 'Node', nodeDef ); + + if ( extensionNode !== null ) { + + pending.push( extensionNode ); + + } + if ( nodeDef.mesh !== undefined ) { pending.push( parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { @@ -3095,16 +3130,6 @@ var GLTFLoader = ( function () { node = mesh.clone(); node.name += '_instance_' + instanceNum; - // onBeforeRender copy for Specular-Glossiness - node.onBeforeRender = mesh.onBeforeRender; - - for ( var i = 0, il = node.children.length; i < il; i ++ ) { - - node.children[ i ].name += '_instance_' + instanceNum; - node.children[ i ].onBeforeRender = mesh.children[ i ].onBeforeRender; - - } - } else { node = mesh; @@ -3140,14 +3165,6 @@ var GLTFLoader = ( function () { } - if ( nodeDef.extensions - && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] - && nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) { - - pending.push( parser.getDependency( 'light', nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light ) ); - - } - return Promise.all( pending ); }() ).then( function ( objects ) { @@ -3336,7 +3353,7 @@ var GLTFLoader = ( function () { return function loadScene( sceneIndex ) { var json = this.json; - var extensions = this.extensions; + var extensions = this.plugins.extensions; var sceneDef = this.json.scenes[ sceneIndex ]; var parser = this;