diff --git a/docs/examples/loaders/GLTF2Loader.html b/docs/examples/loaders/GLTFLoader.html similarity index 95% rename from docs/examples/loaders/GLTF2Loader.html rename to docs/examples/loaders/GLTFLoader.html index 4a9af9ceb07c2dfb5ed977fd21450d803c31352e..9629881573f0b9f1a9e2c6513fd9de9a56cbc687 100644 --- a/docs/examples/loaders/GLTF2Loader.html +++ b/docs/examples/loaders/GLTFLoader.html @@ -25,7 +25,7 @@

Extensions

- GLTF2Loader supports the following glTF extensions: + GLTFLoader supports the following glTF extensions:
diff --git a/editor/index.html b/editor/index.html index 782fa084a62abef144f451c67c44090c264cc55b..a4f60529b37cbe8f8213ed5ed018e950d631d01b 100644 --- a/editor/index.html +++ b/editor/index.html @@ -23,7 +23,7 @@ - + diff --git a/editor/js/Loader.js b/editor/js/Loader.js index 3219e0ace1f748d30157fc26afb7492c1e7ddb7a..3be687970b023db755fb9d1170f170c0222c5f3a 100644 --- a/editor/js/Loader.js +++ b/editor/js/Loader.js @@ -176,7 +176,7 @@ var Loader = function ( editor ) { var contents = event.target.result; - var loader = new THREE.GLTF2Loader(); + var loader = new THREE.GLTFLoader(); loader.parse( contents, '', function ( result ) { result.scene.name = filename; diff --git a/examples/files.js b/examples/files.js index c5e3317a6c0a986f79e3fdfbbd13209354a6eeea..38a2d23edf7071cd759da26ac0ba8801553daa4b 100644 --- a/examples/files.js +++ b/examples/files.js @@ -18,7 +18,7 @@ var files = { "webgl_effects_parallaxbarrier", "webgl_effects_peppersghost", "webgl_effects_stereo", - "webgl_exporter_gltf2", + "webgl_exporter_gltf", "webgl_exporter_obj", "webgl_geometries", "webgl_geometries2", @@ -92,7 +92,7 @@ var files = { "webgl_loader_ctm_materials", "webgl_loader_draco", "webgl_loader_fbx", - "webgl_loader_gltf2", + "webgl_loader_gltf", "webgl_loader_imagebitmap", "webgl_loader_json_blender", "webgl_loader_json_claraio", diff --git a/examples/js/exporters/GLTFExporter.js b/examples/js/exporters/GLTFExporter.js index 9f92f2f4dd0b0881fd4a271043697816e78df387..8d80f7c0bd073dd21e9f9946af06069cb1f52ff5 100644 --- a/examples/js/exporters/GLTFExporter.js +++ b/examples/js/exporters/GLTFExporter.js @@ -233,7 +233,7 @@ THREE.GLTFExporter.prototype = { } else { - throw new Error( 'THREE.GLTF2Exporter: Unsupported bufferAttribute component type.' ); + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); } diff --git a/examples/js/loaders/GLTF2Loader.js b/examples/js/loaders/GLTF2Loader.js deleted file mode 100644 index b40ef3395d0caddd5a18549947c5e379607d5574..0000000000000000000000000000000000000000 --- a/examples/js/loaders/GLTF2Loader.js +++ /dev/null @@ -1,2405 +0,0 @@ -/** - * @author Rich Tibbett / https://github.com/richtr - * @author mrdoob / http://mrdoob.com/ - * @author Tony Parisi / http://www.tonyparisi.com/ - * @author Takahiro / https://github.com/takahirox - * @author Don McCurdy / https://www.donmccurdy.com - */ - -THREE.GLTF2Loader = ( function () { - - function GLTF2Loader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; - - } - - GLTF2Loader.prototype = { - - constructor: GLTF2Loader, - - crossOrigin: 'Anonymous', - - load: function ( url, onLoad, onProgress, onError ) { - - var scope = this; - - var path = this.path && ( typeof this.path === 'string' ) ? this.path : THREE.Loader.prototype.extractUrlBase( url ); - - var loader = new THREE.FileLoader( scope.manager ); - - loader.setResponseType( 'arraybuffer' ); - - loader.load( url, function ( data ) { - - try { - - scope.parse( data, path, onLoad, onError ); - - } catch ( e ) { - - // For SyntaxError or TypeError, return a generic failure message. - onError( e.constructor === Error ? e : new Error( 'THREE.GLTF2Loader: Unable to parse model.' ) ); - - } - - }, onProgress, onError ); - - }, - - setCrossOrigin: function ( value ) { - - this.crossOrigin = value; - - }, - - setPath: function ( value ) { - - this.path = value; - - }, - - parse: function ( data, path, onLoad, onError ) { - - var content; - var extensions = {}; - - var magic = convertUint8ArrayToString( new Uint8Array( data, 0, 4 ) ); - - if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { - - extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); - content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; - - } else { - - content = convertUint8ArrayToString( new Uint8Array( data ) ); - - } - - var json = JSON.parse( content ); - - if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { - - onError( new Error( 'THREE.GLTF2Loader: Legacy glTF detected. Use THREE.GLTFLoader instead.' ) ); - return; - - } - - if ( json.extensionsUsed ) { - - if( json.extensionsUsed.indexOf( EXTENSIONS.KHR_LIGHTS ) >= 0 ) { - - extensions[ EXTENSIONS.KHR_LIGHTS ] = new GLTFLightsExtension( json ); - - } - - if( json.extensionsUsed.indexOf( EXTENSIONS.KHR_MATERIALS_COMMON ) >= 0 ) { - - extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] = new GLTFMaterialsCommonExtension( json ); - - } - - if( json.extensionsUsed.indexOf( EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ) >= 0 ) { - - extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); - - } - - } - - console.time( 'GLTF2Loader' ); - - var parser = new GLTFParser( json, extensions, { - - path: path || this.path, - crossOrigin: this.crossOrigin - - } ); - - parser.parse( function ( scene, scenes, cameras, animations ) { - - console.timeEnd( 'GLTF2Loader' ); - - var glTF = { - scene: scene, - scenes: scenes, - cameras: cameras, - animations: animations - }; - - onLoad( glTF ); - - }, onError ); - - } - - }; - - /* GLTFREGISTRY */ - - function GLTFRegistry() { - - var objects = {}; - - return { - - get: function ( key ) { - - return objects[ key ]; - - }, - - add: function ( key, object ) { - - objects[ key ] = object; - - }, - - remove: function ( key ) { - - delete objects[ key ]; - - }, - - removeAll: function () { - - objects = {}; - - }, - - update: function ( scene, camera ) { - - for ( var name in objects ) { - - var object = objects[ name ]; - - if ( object.update ) { - - object.update( scene, camera ); - - } - - } - - } - - }; - - } - - /*********************************/ - /********** EXTENSIONS ***********/ - /*********************************/ - - var EXTENSIONS = { - KHR_BINARY_GLTF: 'KHR_binary_glTF', - KHR_LIGHTS: 'KHR_lights', - KHR_MATERIALS_COMMON: 'KHR_materials_common', - KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness' - }; - - /** - * Lights Extension - * - * Specification: PENDING - */ - function GLTFLightsExtension( json ) { - - this.name = EXTENSIONS.KHR_LIGHTS; - - this.lights = {}; - - var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS ] ) || {}; - var lights = extension.lights || {}; - - for ( var lightId in lights ) { - - var light = lights[ lightId ]; - var lightNode; - - var color = new THREE.Color().fromArray( light.color ); - - switch ( light.type ) { - - case 'directional': - lightNode = new THREE.DirectionalLight( color ); - lightNode.position.set( 0, 0, 1 ); - break; - - case 'point': - lightNode = new THREE.PointLight( color ); - break; - - case 'spot': - lightNode = new THREE.SpotLight( color ); - lightNode.position.set( 0, 0, 1 ); - break; - - case 'ambient': - lightNode = new THREE.AmbientLight( color ); - break; - - } - - if ( lightNode ) { - - if ( light.constantAttenuation !== undefined ) { - - lightNode.intensity = light.constantAttenuation; - - } - - if ( light.linearAttenuation !== undefined ) { - - lightNode.distance = 1 / light.linearAttenuation; - - } - - if ( light.quadraticAttenuation !== undefined ) { - - lightNode.decay = light.quadraticAttenuation; - - } - - if ( light.fallOffAngle !== undefined ) { - - lightNode.angle = light.fallOffAngle; - - } - - if ( light.fallOffExponent !== undefined ) { - - console.warn( 'THREE.GLTF2Loader:: light.fallOffExponent not currently supported.' ); - - } - - lightNode.name = light.name || ( 'light_' + lightId ); - this.lights[ lightId ] = lightNode; - - } - - } - - } - - /** - * Common Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common - */ - function GLTFMaterialsCommonExtension( json ) { - - this.name = EXTENSIONS.KHR_MATERIALS_COMMON; - - } - - GLTFMaterialsCommonExtension.prototype.getMaterialType = function ( material ) { - - var khrMaterial = material.extensions[ this.name ]; - - switch ( khrMaterial.type ) { - - case 'commonBlinn' : - case 'commonPhong' : - return THREE.MeshPhongMaterial; - - case 'commonLambert' : - return THREE.MeshLambertMaterial; - - case 'commonConstant' : - default : - return THREE.MeshBasicMaterial; - - } - - }; - - GLTFMaterialsCommonExtension.prototype.extendParams = function ( materialParams, material, parser ) { - - var khrMaterial = material.extensions[ this.name ]; - - var pending = []; - - var keys = []; - - // TODO: Currently ignored: 'ambientFactor', 'ambientTexture' - switch ( khrMaterial.type ) { - - case 'commonBlinn' : - case 'commonPhong' : - keys.push( 'diffuseFactor', 'diffuseTexture', 'specularFactor', 'specularTexture', 'shininessFactor' ); - break; - - case 'commonLambert' : - keys.push( 'diffuseFactor', 'diffuseTexture' ); - break; - - case 'commonConstant' : - default : - break; - - } - - var materialValues = {}; - - keys.forEach( function( v ) { - - if ( khrMaterial[ v ] !== undefined ) materialValues[ v ] = khrMaterial[ v ]; - - } ); - - if ( materialValues.diffuseFactor !== undefined ) { - - materialParams.color = new THREE.Color().fromArray( materialValues.diffuseFactor ); - materialParams.opacity = materialValues.diffuseFactor[ 3 ]; - - } - - if ( materialValues.diffuseTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', materialValues.diffuseTexture.index ) ); - - } - - if ( materialValues.specularFactor !== undefined ) { - - materialParams.specular = new THREE.Color().fromArray( materialValues.specularFactor ); - - } - - if ( materialValues.specularTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'specularMap', materialValues.specularTexture.index ) ); - - } - - if ( materialValues.shininessFactor !== undefined ) { - - materialParams.shininess = materialValues.shininessFactor; - - } - - return Promise.all( pending ); - - }; - - /* BINARY EXTENSION */ - - var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF'; - var BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; - var BINARY_EXTENSION_HEADER_LENGTH = 12; - var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; - - function GLTFBinaryExtension( data ) { - - this.name = EXTENSIONS.KHR_BINARY_GLTF; - this.content = null; - this.body = null; - - var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); - - this.header = { - magic: convertUint8ArrayToString( new Uint8Array( data.slice( 0, 4 ) ) ), - version: headerView.getUint32( 4, true ), - length: headerView.getUint32( 8, true ) - }; - - if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { - - throw new Error( 'THREE.GLTF2Loader: Unsupported glTF-Binary header.' ); - - } else if ( this.header.version < 2.0 ) { - - throw new Error( 'THREE.GLTF2Loader: Legacy binary file detected. Use GLTFLoader instead.' ); - - } - - var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); - var chunkIndex = 0; - - while ( chunkIndex < chunkView.byteLength ) { - - var chunkLength = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - var chunkType = chunkView.getUint32( chunkIndex, true ); - chunkIndex += 4; - - if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { - - var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); - this.content = convertUint8ArrayToString( contentArray ); - - } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - - var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; - this.body = data.slice( byteOffset, byteOffset + chunkLength ); - - } - - // Clients must ignore chunks with unknown types. - - chunkIndex += chunkLength; - - } - - if ( this.content === null ) { - - throw new Error( 'THREE.GLTF2Loader: JSON content not found.' ); - - } - - } - - /** - * Specular-Glossiness Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness - */ - function GLTFMaterialsPbrSpecularGlossinessExtension() { - - return { - - name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, - - getMaterialType: function () { - - return THREE.ShaderMaterial; - - }, - - extendParams: function ( params, material, parser ) { - - var pbrSpecularGlossiness = material.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 );', - ' // 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 fragmentShader = shader.fragmentShader - .replace( '#include ', '' ) - .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 }; - - params.vertexShader = shader.vertexShader; - params.fragmentShader = fragmentShader; - params.uniforms = uniforms; - params.defines = { 'STANDARD': '' }; - - params.color = new THREE.Color( 1.0, 1.0, 1.0 ); - params.opacity = 1.0; - - var pending = []; - - if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { - - var array = pbrSpecularGlossiness.diffuseFactor; - - params.color.fromArray( array ); - params.opacity = array[ 3 ]; - - } - - if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { - - pending.push( parser.assignTexture( params, 'map', pbrSpecularGlossiness.diffuseTexture.index ) ); - - } - - params.emissive = new THREE.Color( 0.0, 0.0, 0.0 ); - params.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; - params.specular = new THREE.Color( 1.0, 1.0, 1.0 ); - - if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { - - params.specular.fromArray( pbrSpecularGlossiness.specularFactor ); - - } - - if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { - - var specGlossIndex = pbrSpecularGlossiness.specularGlossinessTexture.index; - pending.push( parser.assignTexture( params, 'glossinessMap', specGlossIndex ) ); - pending.push( parser.assignTexture( params, 'specularMap', specGlossIndex ) ); - - } - - return Promise.all( pending ); - - }, - - createMaterial: function ( params ) { - - // setup material properties based on MeshStandardMaterial for Specular-Glossiness - - 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; - - material.color = params.color; - - material.map = params.map === undefined ? null : params.map; - - material.lightMap = null; - material.lightMapIntensity = 1.0; - - material.aoMap = params.aoMap === undefined ? null : params.aoMap; - material.aoMapIntensity = 1.0; - - material.emissive = params.emissive; - material.emissiveIntensity = 1.0; - material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap; - - material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap; - material.bumpScale = 1; - - material.normalMap = params.normalMap === undefined ? null : params.normalMap; - material.normalScale = new THREE.Vector2( 1, 1 ); - - material.displacementMap = null; - material.displacementScale = 1; - material.displacementBias = 0; - - material.specularMap = params.specularMap === undefined ? null : params.specularMap; - material.specular = params.specular; - - 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; - - material.refractionRatio = 0.98; - - material.extensions.derivatives = true; - - return material; - - }, - - // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. - refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) { - - 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 ); - - 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; - - // uv repeat and offset setting priorities - // 1. color map - // 2. specular map - // 3. normal map - // 4. bump map - // 5. alpha map - // 6. emissive map - - var uvScaleMap; - - if ( material.map ) { - - uvScaleMap = material.map; - - } else if ( material.specularMap ) { - - uvScaleMap = material.specularMap; - - } else if ( material.displacementMap ) { - - uvScaleMap = material.displacementMap; - - } else if ( material.normalMap ) { - - uvScaleMap = material.normalMap; - - } else if ( material.bumpMap ) { - - uvScaleMap = material.bumpMap; - - } else if ( material.glossinessMap ) { - - uvScaleMap = material.glossinessMap; - - } else if ( material.alphaMap ) { - - uvScaleMap = material.alphaMap; - - } else if ( material.emissiveMap ) { - - uvScaleMap = material.emissiveMap; - - } - - if ( uvScaleMap !== undefined ) { - - // backwards compatibility - if ( uvScaleMap.isWebGLRenderTarget ) { - - uvScaleMap = uvScaleMap.texture; - - } - - var offset = uvScaleMap.offset; - var repeat = uvScaleMap.repeat; - - uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); - - } - - uniforms.envMap.value = material.envMap; - uniforms.envMapIntensity.value = material.envMapIntensity; - uniforms.flipEnvMap.value = ( material.envMap && material.envMap.isCubeTexture ) ? -1 : 1; - - uniforms.refractionRatio.value = material.refractionRatio; - - uniforms.specular.value.copy( material.specular ); - uniforms.glossiness.value = material.glossiness; - - uniforms.glossinessMap.value = material.glossinessMap; - - uniforms.emissiveMap.value = material.emissiveMap; - uniforms.bumpMap.value = material.bumpMap; - uniforms.normalMap.value = material.normalMap; - - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { - - defines.USE_GLOSSINESSMAP = ''; - // set USE_ROUGHNESSMAP to enable vUv - defines.USE_ROUGHNESSMAP = ''; - - } - - if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { - - delete defines.USE_GLOSSINESSMAP; - delete defines.USE_ROUGHNESSMAP; - - } - - } - - }; - - } - - /*********************************/ - /********** INTERNALS ************/ - /*********************************/ - - /* CONSTANTS */ - - var WEBGL_CONSTANTS = { - FLOAT: 5126, - //FLOAT_MAT2: 35674, - FLOAT_MAT3: 35675, - FLOAT_MAT4: 35676, - FLOAT_VEC2: 35664, - FLOAT_VEC3: 35665, - FLOAT_VEC4: 35666, - LINEAR: 9729, - REPEAT: 10497, - SAMPLER_2D: 35678, - POINTS: 0, - LINES: 1, - LINE_LOOP: 2, - LINE_STRIP: 3, - TRIANGLES: 4, - TRIANGLE_STRIP: 5, - TRIANGLE_FAN: 6, - UNSIGNED_BYTE: 5121, - UNSIGNED_SHORT: 5123 - }; - - var WEBGL_TYPE = { - 5126: Number, - //35674: THREE.Matrix2, - 35675: THREE.Matrix3, - 35676: THREE.Matrix4, - 35664: THREE.Vector2, - 35665: THREE.Vector3, - 35666: THREE.Vector4, - 35678: THREE.Texture - }; - - var WEBGL_COMPONENT_TYPES = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5125: Uint32Array, - 5126: Float32Array - }; - - var WEBGL_FILTERS = { - 9728: THREE.NearestFilter, - 9729: THREE.LinearFilter, - 9984: THREE.NearestMipMapNearestFilter, - 9985: THREE.LinearMipMapNearestFilter, - 9986: THREE.NearestMipMapLinearFilter, - 9987: THREE.LinearMipMapLinearFilter - }; - - var WEBGL_WRAPPINGS = { - 33071: THREE.ClampToEdgeWrapping, - 33648: THREE.MirroredRepeatWrapping, - 10497: THREE.RepeatWrapping - }; - - var WEBGL_TEXTURE_FORMATS = { - 6406: THREE.AlphaFormat, - 6407: THREE.RGBFormat, - 6408: THREE.RGBAFormat, - 6409: THREE.LuminanceFormat, - 6410: THREE.LuminanceAlphaFormat - }; - - var WEBGL_TEXTURE_DATATYPES = { - 5121: THREE.UnsignedByteType, - 32819: THREE.UnsignedShort4444Type, - 32820: THREE.UnsignedShort5551Type, - 33635: THREE.UnsignedShort565Type - }; - - var WEBGL_SIDES = { - 1028: THREE.BackSide, // Culling front - 1029: THREE.FrontSide // Culling back - //1032: THREE.NoSide // Culling front and back, what to do? - }; - - var WEBGL_DEPTH_FUNCS = { - 512: THREE.NeverDepth, - 513: THREE.LessDepth, - 514: THREE.EqualDepth, - 515: THREE.LessEqualDepth, - 516: THREE.GreaterEqualDepth, - 517: THREE.NotEqualDepth, - 518: THREE.GreaterEqualDepth, - 519: THREE.AlwaysDepth - }; - - var WEBGL_BLEND_EQUATIONS = { - 32774: THREE.AddEquation, - 32778: THREE.SubtractEquation, - 32779: THREE.ReverseSubtractEquation - }; - - var WEBGL_BLEND_FUNCS = { - 0: THREE.ZeroFactor, - 1: THREE.OneFactor, - 768: THREE.SrcColorFactor, - 769: THREE.OneMinusSrcColorFactor, - 770: THREE.SrcAlphaFactor, - 771: THREE.OneMinusSrcAlphaFactor, - 772: THREE.DstAlphaFactor, - 773: THREE.OneMinusDstAlphaFactor, - 774: THREE.DstColorFactor, - 775: THREE.OneMinusDstColorFactor, - 776: THREE.SrcAlphaSaturateFactor - // The followings are not supported by Three.js yet - //32769: CONSTANT_COLOR, - //32770: ONE_MINUS_CONSTANT_COLOR, - //32771: CONSTANT_ALPHA, - //32772: ONE_MINUS_CONSTANT_COLOR - }; - - var WEBGL_TYPE_SIZES = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT2': 4, - 'MAT3': 9, - 'MAT4': 16 - }; - - var PATH_PROPERTIES = { - scale: 'scale', - translation: 'position', - rotation: 'quaternion', - weights: 'morphTargetInfluences' - }; - - var INTERPOLATION = { - CATMULLROMSPLINE: THREE.InterpolateSmooth, - CUBICSPLINE: THREE.InterpolateSmooth, - LINEAR: THREE.InterpolateLinear, - STEP: THREE.InterpolateDiscrete - }; - - var STATES_ENABLES = { - 2884: 'CULL_FACE', - 2929: 'DEPTH_TEST', - 3042: 'BLEND', - 3089: 'SCISSOR_TEST', - 32823: 'POLYGON_OFFSET_FILL', - 32926: 'SAMPLE_ALPHA_TO_COVERAGE' - }; - - var ALPHA_MODES = { - OPAQUE: 'OPAQUE', - MASK: 'MASK', - BLEND: 'BLEND' - }; - - /* UTILITY FUNCTIONS */ - - function _each( object, callback, thisObj ) { - - if ( !object ) { - return Promise.resolve(); - } - - var results; - var fns = []; - - if ( Object.prototype.toString.call( object ) === '[object Array]' ) { - - results = []; - - var length = object.length; - - for ( var idx = 0; idx < length; idx ++ ) { - - var value = callback.call( thisObj || this, object[ idx ], idx ); - - if ( value ) { - - fns.push( value ); - - if ( value instanceof Promise ) { - - value.then( function( key, value ) { - - results[ key ] = value; - - }.bind( this, idx )); - - } else { - - results[ idx ] = value; - - } - - } - - } - - } else { - - results = {}; - - for ( var key in object ) { - - if ( object.hasOwnProperty( key ) ) { - - var value = callback.call( thisObj || this, object[ key ], key ); - - if ( value ) { - - fns.push( value ); - - if ( value instanceof Promise ) { - - value.then( function( key, value ) { - - results[ key ] = value; - - }.bind( this, key )); - - } else { - - results[ key ] = value; - - } - - } - - } - - } - - } - - return Promise.all( fns ).then( function() { - - return results; - - }); - - } - - function resolveURL( url, path ) { - - // Invalid URL - if ( typeof url !== 'string' || url === '' ) - return ''; - - // Absolute URL http://,https://,// - if ( /^(https?:)?\/\//i.test( url ) ) { - - return url; - - } - - // Data URI - if ( /^data:.*,.*$/i.test( url ) ) { - - return url; - - } - - // Blob URL - if ( /^blob:.*$/i.test( url ) ) { - - return url; - - } - - // Relative URL - return ( path || '' ) + url; - - } - - function convertUint8ArrayToString( array ) { - - if ( window.TextDecoder !== undefined ) { - - return new TextDecoder().decode( array ); - - } - - // Avoid the String.fromCharCode.apply(null, array) shortcut, which - // throws a "maximum call stack size exceeded" error for large arrays. - - var s = ''; - - for ( var i = 0, il = array.length; i < il; i ++ ) { - - s += String.fromCharCode( array[ i ] ); - - } - - return s; - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material - */ - function createDefaultMaterial() { - - return new THREE.MeshStandardMaterial( { - color: 0xFFFFFF, - emissive: 0x000000, - metalness: 1, - roughness: 1, - transparent: false, - depthTest: true, - side: THREE.FrontSide - } ); - - } - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets - * @param {THREE.Mesh} mesh - * @param {GLTF.Mesh} meshDef - * @param {GLTF.Primitive} primitiveDef - * @param {Object} dependencies - */ - function addMorphTargets ( mesh, meshDef, primitiveDef, dependencies ) { - - var geometry = mesh.geometry; - var material = mesh.material; - - var targets = primitiveDef.targets; - var morphAttributes = geometry.morphAttributes; - - morphAttributes.position = []; - morphAttributes.normal = []; - - material.morphTargets = true; - - for ( var i = 0, il = targets.length; i < il; i ++ ) { - - var target = targets[ i ]; - var attributeName = 'morphTarget' + i; - - var positionAttribute, normalAttribute; - - if ( target.POSITION !== undefined ) { - - // Three.js morph formula is - // position - // + weight0 * ( morphTarget0 - position ) - // + weight1 * ( morphTarget1 - position ) - // ... - // while the glTF one is - // position - // + weight0 * morphTarget0 - // + weight1 * morphTarget1 - // ... - // then adding position to morphTarget. - // So morphTarget value will depend on mesh's position, then cloning attribute - // for the case if attribute is shared among two or more meshes. - - positionAttribute = dependencies.accessors[ target.POSITION ].clone(); - var position = geometry.attributes.position; - - for ( var j = 0, jl = positionAttribute.count; j < jl; j ++ ) { - - positionAttribute.setXYZ( - j, - positionAttribute.getX( j ) + position.getX( j ), - positionAttribute.getY( j ) + position.getY( j ), - positionAttribute.getZ( j ) + position.getZ( j ) - ); - - } - - } else { - - // Copying the original position not to affect the final position. - // See the formula above. - positionAttribute = geometry.attributes.position.clone(); - - } - - if ( target.NORMAL !== undefined ) { - - material.morphNormals = true; - - // see target.POSITION's comment - - normalAttribute = dependencies.accessors[ target.NORMAL ].clone(); - var normal = geometry.attributes.normal; - - for ( var j = 0, jl = normalAttribute.count; j < jl; j ++ ) { - - normalAttribute.setXYZ( - j, - normalAttribute.getX( j ) + normal.getX( j ), - normalAttribute.getY( j ) + normal.getY( j ), - normalAttribute.getZ( j ) + normal.getZ( j ) - ); - - } - - } else { - - normalAttribute = geometry.attributes.normal.clone(); - - } - - if ( target.TANGENT !== undefined ) { - - // TODO: implement - - } - - positionAttribute.name = attributeName; - normalAttribute.name = attributeName; - - morphAttributes.position.push( positionAttribute ); - morphAttributes.normal.push( normalAttribute ); - - } - - mesh.updateMorphTargets(); - - if ( meshDef.weights !== undefined ) { - - for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { - - mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; - - } - - } - - } - - /* GLTF PARSER */ - - function GLTFParser( json, extensions, options ) { - - this.json = json || {}; - this.extensions = extensions || {}; - this.options = options || {}; - - // loader object cache - this.cache = new GLTFRegistry(); - - } - - GLTFParser.prototype._withDependencies = function ( dependencies ) { - - var _dependencies = {}; - - for ( var i = 0; i < dependencies.length; i ++ ) { - - var dependency = dependencies[ i ]; - var fnName = 'load' + dependency.charAt( 0 ).toUpperCase() + dependency.slice( 1 ); - - var cached = this.cache.get( dependency ); - - if ( cached !== undefined ) { - - _dependencies[ dependency ] = cached; - - } else if ( this[ fnName ] ) { - - var fn = this[ fnName ](); - this.cache.add( dependency, fn ); - - _dependencies[ dependency ] = fn; - - } - - } - - return _each( _dependencies, function ( dependency ) { - - return dependency; - - } ); - - }; - - GLTFParser.prototype.parse = function ( onLoad, onError ) { - - var json = this.json; - - // Clear the loader cache - this.cache.removeAll(); - - // Fire the callback on complete - this._withDependencies( [ - - 'scenes', - 'cameras', - 'animations' - - ] ).then( function ( dependencies ) { - - var scenes = []; - - for ( var name in dependencies.scenes ) { - - scenes.push( dependencies.scenes[ name ] ); - - } - - var scene = json.scene !== undefined ? dependencies.scenes[ json.scene ] : scenes[ 0 ]; - - var cameras = []; - - for ( var name in dependencies.cameras ) { - - var camera = dependencies.cameras[ name ]; - cameras.push( camera ); - - } - - var animations = []; - - for ( var name in dependencies.animations ) { - - animations.push( dependencies.animations[ name ] ); - - } - - onLoad( scene, scenes, cameras, animations ); - - } ).catch( onError ); - - }; - - /** - * Requests the specified dependency asynchronously, with caching. - * @param {string} type - * @param {number} index - * @return {Promise} - */ - GLTFParser.prototype.getDependency = function ( type, index ) { - - var cacheKey = type + ':' + index; - var dependency = this.cache.get( cacheKey ); - - if ( !dependency ) { - - var fnName = 'load' + type.charAt( 0 ).toUpperCase() + type.slice( 1 ); - dependency = this[ fnName ]( index ); - this.cache.add( cacheKey, dependency ); - - } - - return dependency; - - }; - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views - * @param {number} bufferIndex - * @return {Promise} - */ - GLTFParser.prototype.loadBuffer = function ( bufferIndex ) { - - var bufferDef = this.json.buffers[ bufferIndex ]; - - if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { - - throw new Error( 'THREE.GLTF2Loader: %s buffer type is not supported.', bufferDef.type ); - - } - - // 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 ); - - } - - var options = this.options; - - return new Promise( function ( resolve ) { - - var loader = new THREE.FileLoader(); - loader.setResponseType( 'arraybuffer' ); - loader.load( resolveURL( bufferDef.uri, options.path ), resolve); - - } ); - - }; - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views - * @param {number} bufferViewIndex - * @return {Promise} - */ - GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) { - - var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; - - return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { - - var byteLength = bufferViewDef.byteLength || 0; - var byteOffset = bufferViewDef.byteOffset || 0; - return buffer.slice( byteOffset, byteOffset + byteLength ); - - } ); - - }; - - GLTFParser.prototype.loadAccessors = function () { - - var parser = this; - var json = this.json; - - return _each( json.accessors, function ( accessor ) { - - return parser.getDependency( 'bufferView', accessor.bufferView ).then( function ( bufferView ) { - - var itemSize = WEBGL_TYPE_SIZES[ accessor.type ]; - var TypedArray = WEBGL_COMPONENT_TYPES[ accessor.componentType ]; - - // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. - var elementBytes = TypedArray.BYTES_PER_ELEMENT; - var itemBytes = elementBytes * itemSize; - var byteStride = json.bufferViews[ accessor.bufferView ].byteStride; - var array; - - // The buffer is not interleaved if the stride is the item size in bytes. - if ( byteStride && byteStride !== itemBytes ) { - - // Use the full buffer if it's interleaved. - array = new TypedArray( bufferView ); - - // Integer parameters to IB/IBA are in array elements, not bytes. - var ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes ); - - return new THREE.InterleavedBufferAttribute( ib, itemSize, accessor.byteOffset / elementBytes ); - - } else { - - array = new TypedArray( bufferView, accessor.byteOffset, accessor.count * itemSize ); - - return new THREE.BufferAttribute( array, itemSize ); - - } - - } ); - - } ); - - }; - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures - * @param {number} textureIndex - * @return {Promise} - */ - GLTFParser.prototype.loadTexture = function ( textureIndex ) { - - var parser = this; - var json = this.json; - var options = this.options; - - var URL = window.URL || window.webkitURL; - - var textureDef = json.textures[ textureIndex ]; - var source = json.images[ textureDef.source ]; - var sourceURI = source.uri; - var isObjectURL = false; - - if ( source.bufferView !== undefined ) { - - // Load binary image data from bufferView, if provided. - - sourceURI = parser.getDependency( 'bufferView', source.bufferView ) - .then( function ( bufferView ) { - - isObjectURL = true; - var blob = new Blob( [ bufferView ], { type: source.mimeType } ); - sourceURI = URL.createObjectURL( blob ); - return sourceURI; - - } ); - - } - - return Promise.resolve( sourceURI ).then( function ( sourceURI ) { - - // Load Texture resource. - - var textureLoader = THREE.Loader.Handlers.get( sourceURI ) || new THREE.TextureLoader(); - textureLoader.setCrossOrigin( options.crossOrigin ); - - return new Promise( function ( resolve, reject ) { - - textureLoader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); - - } ); - - } ).then( function ( texture ) { - - // Clean up resources and configure Texture. - - if ( isObjectURL !== undefined ) { - - URL.revokeObjectURL( sourceURI ); - - } - - texture.flipY = false; - - if ( textureDef.name !== undefined ) texture.name = textureDef.name; - - texture.format = textureDef.format !== undefined ? WEBGL_TEXTURE_FORMATS[ textureDef.format ] : THREE.RGBAFormat; - - if ( textureDef.internalFormat !== undefined && texture.format !== WEBGL_TEXTURE_FORMATS[ textureDef.internalFormat ] ) { - - console.warn( 'THREE.GLTF2Loader: Three.js does not support texture internalFormat which is different from texture format. ' + - 'internalFormat will be forced to be the same value as format.' ); - - } - - texture.type = textureDef.type !== undefined ? WEBGL_TEXTURE_DATATYPES[ textureDef.type ] : THREE.UnsignedByteType; - - var samplers = json.samplers || {}; - var sampler = samplers[ textureDef.sampler ] || {}; - - texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter; - texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipMapLinearFilter; - texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping; - texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping; - - return texture; - - } ); - - }; - - /** - * Asynchronously assigns a texture to the given material parameters. - * @param {Object} materialParams - * @param {string} textureName - * @param {number} textureIndex - * @return {Promise} - */ - GLTFParser.prototype.assignTexture = function ( materialParams, textureName, textureIndex ) { - - return this.getDependency( 'texture', textureIndex ).then( function ( texture ) { - - materialParams[ textureName ] = texture; - - } ); - - }; - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials - * @return {Promise>} - */ - GLTFParser.prototype.loadMaterials = function () { - - var parser = this; - var json = this.json; - var extensions = this.extensions; - - return _each( json.materials, function ( material ) { - - var materialType; - var materialParams = {}; - var materialExtensions = material.extensions || {}; - - var pending = []; - - if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] ) { - - var khcExtension = extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ]; - materialType = khcExtension.getMaterialType( material ); - pending.push( khcExtension.extendParams( materialParams, material, parser ) ); - - } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { - - var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; - materialType = sgExtension.getMaterialType( material ); - pending.push( sgExtension.extendParams( materialParams, material, parser ) ); - - } else if ( material.pbrMetallicRoughness !== undefined ) { - - // Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material - - materialType = THREE.MeshStandardMaterial; - - var metallicRoughness = material.pbrMetallicRoughness; - - materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; - - if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - - var array = metallicRoughness.baseColorFactor; - - materialParams.color.fromArray( array ); - materialParams.opacity = array[ 3 ]; - - } - - if ( metallicRoughness.baseColorTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture.index ) ); - - } - - materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; - materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; - - if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { - - var textureIndex = metallicRoughness.metallicRoughnessTexture.index; - pending.push( parser.assignTexture( materialParams, 'metalnessMap', textureIndex ) ); - pending.push( parser.assignTexture( materialParams, 'roughnessMap', textureIndex ) ); - - } - - } else { - - materialType = THREE.MeshPhongMaterial; - - } - - if ( material.doubleSided === true ) { - - materialParams.side = THREE.DoubleSide; - - } - - var alphaMode = material.alphaMode || ALPHA_MODES.OPAQUE; - - if ( alphaMode !== ALPHA_MODES.OPAQUE ) { - - materialParams.transparent = true; - - } else { - - materialParams.transparent = false; - - } - - if ( material.normalTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'normalMap', material.normalTexture.index ) ); - - } - - if ( material.occlusionTexture !== undefined ) { - - pending.push( parser.assignTexture( materialParams, 'aoMap', material.occlusionTexture.index ) ); - - } - - if ( material.emissiveFactor !== undefined ) { - - if ( materialType === THREE.MeshBasicMaterial ) { - - materialParams.color = new THREE.Color().fromArray( material.emissiveFactor ); - - } else { - - materialParams.emissive = new THREE.Color().fromArray( material.emissiveFactor ); - - } - - } - - if ( material.emissiveTexture !== undefined ) { - - if ( materialType === THREE.MeshBasicMaterial ) { - - pending.push( parser.assignTexture( materialParams, 'map', material.emissiveTexture.index ) ); - - } else { - - pending.push( parser.assignTexture( materialParams, 'emissiveMap', material.emissiveTexture.index ) ); - - } - - } - - return Promise.all( pending ).then( function () { - - var _material; - - if ( materialType === THREE.ShaderMaterial ) { - - _material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); - - } else { - - _material = new materialType( materialParams ); - - } - - if ( material.name !== undefined ) _material.name = material.name; - - // Normal map textures use OpenGL conventions: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materialnormaltexture - _material.normalScale.x = -1; - - _material.userData = material.extras; - - return _material; - - } ); - - } ); - - }; - - GLTFParser.prototype.loadGeometries = function ( primitives ) { - - return this._withDependencies( [ - - 'accessors', - - ] ).then( function ( dependencies ) { - - return _each( primitives, function ( primitive ) { - - var geometry = new THREE.BufferGeometry(); - - var attributes = primitive.attributes; - - for ( var attributeId in attributes ) { - - var attributeEntry = attributes[ attributeId ]; - - if ( attributeEntry === undefined ) return; - - var bufferAttribute = dependencies.accessors[ attributeEntry ]; - - switch ( attributeId ) { - - case 'POSITION': - - geometry.addAttribute( 'position', bufferAttribute ); - break; - - case 'NORMAL': - - geometry.addAttribute( 'normal', bufferAttribute ); - break; - - case 'TEXCOORD_0': - case 'TEXCOORD0': - case 'TEXCOORD': - - geometry.addAttribute( 'uv', bufferAttribute ); - break; - - case 'TEXCOORD_1': - - geometry.addAttribute( 'uv2', bufferAttribute ); - break; - - case 'COLOR_0': - case 'COLOR0': - case 'COLOR': - - geometry.addAttribute( 'color', bufferAttribute ); - break; - - case 'WEIGHTS_0': - case 'WEIGHT': // WEIGHT semantic deprecated. - - geometry.addAttribute( 'skinWeight', bufferAttribute ); - break; - - case 'JOINTS_0': - case 'JOINT': // JOINT semantic deprecated. - - geometry.addAttribute( 'skinIndex', bufferAttribute ); - break; - - } - - } - - if ( primitive.indices !== undefined ) { - - geometry.setIndex( dependencies.accessors[ primitive.indices ] ); - - } - - return geometry; - - } ); - - } ); - - }; - - /** - * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes - */ - GLTFParser.prototype.loadMeshes = function () { - - var scope = this; - var json = this.json; - - return this._withDependencies( [ - - 'accessors', - 'materials' - - ] ).then( function ( dependencies ) { - - return _each( json.meshes, function ( meshDef ) { - - var group = new THREE.Group(); - - if ( meshDef.name !== undefined ) group.name = meshDef.name; - if ( meshDef.extras ) group.userData = meshDef.extras; - - var primitives = meshDef.primitives || []; - - return scope.loadGeometries( primitives ).then( function ( geometries ) { - - for ( var name in primitives ) { - - var primitive = primitives[ name ]; - var geometry = geometries[ name ]; - - var material = primitive.material === undefined - ? createDefaultMaterial() - : dependencies.materials[ primitive.material ]; - - if ( material.aoMap - && geometry.attributes.uv2 === undefined - && geometry.attributes.uv !== undefined ) { - - console.log( 'THREE.GLTF2Loader: Duplicating UVs to support aoMap.' ); - geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); - - } - - if ( geometry.attributes.color !== undefined ) { - - material.vertexColors = THREE.VertexColors; - material.needsUpdate = true; - - } - - if ( geometry.attributes.normal === undefined ) { - - if ( material.flatShading !== undefined ) { - - material.flatShading = true; - - } else { - - // TODO: Remove this backwards-compatibility fix after r87 release. - material.shading = THREE.FlatShading; - - } - - } - - var mesh; - - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === undefined ) { - - mesh = new THREE.Mesh( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - - mesh = new THREE.Mesh( geometry, material ); - mesh.drawMode = THREE.TriangleStripDrawMode; - - } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - - mesh = new THREE.Mesh( geometry, material ); - mesh.drawMode = THREE.TriangleFanDrawMode; - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { - - mesh = new THREE.LineSegments( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { - - mesh = new THREE.Line( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { - - mesh = new THREE.LineLoop( geometry, material ); - - } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { - - mesh = new THREE.Points( geometry, material ); - - } else { - - throw new Error( 'THREE.GLTF2Loader: Primitive mode unsupported: ', primitive.mode ); - - } - - mesh.name = group.name + '_' + name; - - if ( primitive.targets !== undefined ) { - - addMorphTargets( mesh, meshDef, primitive, dependencies ); - - } - - if ( primitive.extras ) mesh.userData = primitive.extras; - - group.add( mesh ); - - } - - return group; - - } ); - - } ); - - } ); - - }; - - /** - * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras - */ - GLTFParser.prototype.loadCameras = function () { - - var json = this.json; - - return _each( json.cameras, function ( camera ) { - - var _camera; - - var params = camera[ camera.type ]; - - if ( !params ) { - - console.warn( 'THREE.GLTF2Loader: Missing camera parameters.' ); - return; - - } - - if ( camera.type === 'perspective' ) { - - var aspectRatio = params.aspectRatio || 1; - var xfov = params.yfov * aspectRatio; - - _camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( xfov ), aspectRatio, params.znear || 1, params.zfar || 2e6 ); - - } else if ( camera.type === 'orthographic' ) { - - _camera = new THREE.OrthographicCamera( params.xmag / -2, params.xmag / 2, params.ymag / 2, params.ymag / -2, params.znear, params.zfar ); - - } - - if ( camera.name !== undefined ) _camera.name = camera.name; - if ( camera.extras ) _camera.userData = camera.extras; - - return _camera; - - } ); - - }; - - GLTFParser.prototype.loadSkins = function () { - - var json = this.json; - - return this._withDependencies( [ - - 'accessors' - - ] ).then( function ( dependencies ) { - - return _each( json.skins, function ( skin ) { - - var _skin = { - joints: skin.joints, - inverseBindMatrices: dependencies.accessors[ skin.inverseBindMatrices ] - }; - - return _skin; - - } ); - - } ); - - }; - - GLTFParser.prototype.loadAnimations = function () { - - var json = this.json; - - return this._withDependencies( [ - - 'accessors', - 'nodes' - - ] ).then( function ( dependencies ) { - - return _each( json.animations, function ( animation, animationId ) { - - var tracks = []; - - for ( var channelId in animation.channels ) { - - var channel = animation.channels[ channelId ]; - var sampler = animation.samplers[ channel.sampler ]; - - if ( sampler ) { - - var target = channel.target; - var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. - var input = animation.parameters !== undefined ? animation.parameters[ sampler.input ] : sampler.input; - var output = animation.parameters !== undefined ? animation.parameters[ sampler.output ] : sampler.output; - - var inputAccessor = dependencies.accessors[ input ]; - var outputAccessor = dependencies.accessors[ output ]; - - var node = dependencies.nodes[ name ]; - - if ( node ) { - - node.updateMatrix(); - node.matrixAutoUpdate = true; - - var TypedKeyframeTrack; - - switch ( PATH_PROPERTIES[ target.path ] ) { - - case PATH_PROPERTIES.weights: - - TypedKeyframeTrack = THREE.NumberKeyframeTrack; - break; - - case PATH_PROPERTIES.rotation: - - TypedKeyframeTrack = THREE.QuaternionKeyframeTrack; - break; - - case PATH_PROPERTIES.position: - case PATH_PROPERTIES.scale: - default: - - TypedKeyframeTrack = THREE.VectorKeyframeTrack; - break; - - } - - var targetName = node.name ? node.name : node.uuid; - - if ( sampler.interpolation === 'CATMULLROMSPLINE' ) { - - console.warn( 'THREE.GLTF2Loader: CATMULLROMSPLINE interpolation is not supported. Using CUBICSPLINE instead.' ); - - } - - var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear; - - var targetNames = []; - - if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { - - // node should be THREE.Group here but - // PATH_PROPERTIES.weights(morphTargetInfluences) should be - // the property of a mesh object under node. - // So finding targets here. - - node.traverse( function ( object ) { - - if ( object.isMesh === true && object.material.morphTargets === true ) { - - targetNames.push( object.name ? object.name : object.uuid ); - - } - - } ); - - } else { - - targetNames.push( targetName ); - - } - - // KeyframeTrack.optimize() will modify given 'times' and 'values' - // buffers before creating a truncated copy to keep. Because buffers may - // be reused by other tracks, make copies here. - for ( var i = 0, il = targetNames.length; i < il; i ++ ) { - - tracks.push( new TypedKeyframeTrack( - targetNames[ i ] + '.' + PATH_PROPERTIES[ target.path ], - THREE.AnimationUtils.arraySlice( inputAccessor.array, 0 ), - THREE.AnimationUtils.arraySlice( outputAccessor.array, 0 ), - interpolation - ) ); - - } - - } - - } - - } - - var name = animation.name !== undefined ? animation.name : 'animation_' + animationId; - - return new THREE.AnimationClip( name, undefined, tracks ); - - } ); - - } ); - - }; - - GLTFParser.prototype.loadNodes = function () { - - var json = this.json; - var extensions = this.extensions; - var scope = this; - - var nodes = json.nodes || []; - var skins = json.skins || []; - - // Nothing in the node definition indicates whether it is a Bone or an - // Object3D. Use the skins' joint references to mark bones. - skins.forEach( function ( skin ) { - - skin.joints.forEach( function ( id ) { - - nodes[ id ].isBone = true; - - } ); - - } ); - - return _each( json.nodes, function ( node ) { - - var matrix = new THREE.Matrix4(); - - var _node = node.isBone === true ? new THREE.Bone() : new THREE.Object3D(); - - if ( node.name !== undefined ) { - - _node.name = THREE.PropertyBinding.sanitizeNodeName( node.name ); - - } - - if ( node.extras ) _node.userData = node.extras; - - if ( node.matrix !== undefined ) { - - matrix.fromArray( node.matrix ); - _node.applyMatrix( matrix ); - - } else { - - if ( node.translation !== undefined ) { - - _node.position.fromArray( node.translation ); - - } - - if ( node.rotation !== undefined ) { - - _node.quaternion.fromArray( node.rotation ); - - } - - if ( node.scale !== undefined ) { - - _node.scale.fromArray( node.scale ); - - } - - } - - return _node; - - } ).then( function ( __nodes ) { - - return scope._withDependencies( [ - - 'meshes', - 'skins', - 'cameras' - - ] ).then( function ( dependencies ) { - - return _each( __nodes, function ( _node, nodeId ) { - - var node = json.nodes[ nodeId ]; - - var meshes; - - if ( node.mesh !== undefined) { - - meshes = [ node.mesh ]; - - } else if ( node.meshes !== undefined ) { - - console.warn( 'THREE.GLTF2Loader: Legacy glTF file detected. Nodes may have no more than one mesh.' ); - - meshes = node.meshes; - - } - - if ( meshes !== undefined ) { - - for ( var meshId in meshes ) { - - var mesh = meshes[ meshId ]; - var group = dependencies.meshes[ mesh ]; - - if ( group === undefined ) { - - console.warn( 'THREE.GLTF2Loader: Could not find node "' + mesh + '".' ); - continue; - - } - - // do not clone children as they will be replaced anyway - var clonedgroup = group.clone( false ); - - for ( var childrenId in group.children ) { - - var child = group.children[ childrenId ]; - var originalChild = child; - - // clone Mesh to add to _node - - var originalMaterial = child.material; - var originalGeometry = child.geometry; - var originalInfluences = child.morphTargetInfluences; - var originalUserData = child.userData; - var originalName = child.name; - - var material = originalMaterial; - - switch ( child.type ) { - - case 'LineSegments': - child = new THREE.LineSegments( originalGeometry, material ); - break; - - case 'LineLoop': - child = new THREE.LineLoop( originalGeometry, material ); - break; - - case 'Line': - child = new THREE.Line( originalGeometry, material ); - break; - - case 'Points': - child = new THREE.Points( originalGeometry, material ); - break; - - default: - child = new THREE.Mesh( originalGeometry, material ); - child.drawMode = originalChild.drawMode; - - } - - child.castShadow = true; - child.morphTargetInfluences = originalInfluences; - child.userData = originalUserData; - child.name = originalName; - - var skinEntry; - - if ( node.skin !== undefined ) { - - skinEntry = dependencies.skins[ node.skin ]; - - } - - // Replace Mesh with SkinnedMesh in library - if ( skinEntry ) { - - var geometry = originalGeometry; - material = originalMaterial; - material.skinning = true; - - child = new THREE.SkinnedMesh( geometry, material ); - child.castShadow = true; - child.userData = originalUserData; - child.name = originalName; - - var bones = []; - var boneInverses = []; - - for ( var i = 0, l = skinEntry.joints.length; i < l; i ++ ) { - - var jointId = skinEntry.joints[ i ]; - var jointNode = __nodes[ jointId ]; - - if ( jointNode ) { - - bones.push( jointNode ); - - var m = skinEntry.inverseBindMatrices.array; - var mat = new THREE.Matrix4().fromArray( m, i * 16 ); - boneInverses.push( mat ); - - } else { - - console.warn( 'THREE.GLTF2Loader: Joint "%s" could not be found.', jointId ); - - } - - } - - child.bind( new THREE.Skeleton( bones, boneInverses ), child.matrixWorld ); - - } - - clonedgroup.add( child ); - - } - - _node.add( clonedgroup ); - - } - - } - - if ( node.camera !== undefined ) { - - var camera = dependencies.cameras[ node.camera ]; - - _node.add( camera ); - - } - - if ( node.extensions - && node.extensions[ EXTENSIONS.KHR_LIGHTS ] - && node.extensions[ EXTENSIONS.KHR_LIGHTS ].light !== undefined ) { - - var lights = extensions[ EXTENSIONS.KHR_LIGHTS ].lights; - _node.add( lights[ node.extensions[ EXTENSIONS.KHR_LIGHTS ].light ] ); - - } - - return _node; - - } ); - - } ); - - } ); - - }; - - GLTFParser.prototype.loadScenes = function () { - - var json = this.json; - var extensions = this.extensions; - - // scene node hierachy builder - - function buildNodeHierachy( nodeId, parentObject, allNodes ) { - - var _node = allNodes[ nodeId ]; - parentObject.add( _node ); - - var node = json.nodes[ nodeId ]; - - if ( node.children ) { - - var children = node.children; - - for ( var i = 0, l = children.length; i < l; i ++ ) { - - var child = children[ i ]; - buildNodeHierachy( child, _node, allNodes ); - - } - - } - - } - - return this._withDependencies( [ - - 'nodes' - - ] ).then( function ( dependencies ) { - - return _each( json.scenes, function ( scene ) { - - var _scene = new THREE.Scene(); - if ( scene.name !== undefined ) _scene.name = scene.name; - - if ( scene.extras ) _scene.userData = scene.extras; - - var nodes = scene.nodes || []; - - for ( var i = 0, l = nodes.length; i < l; i ++ ) { - - var nodeId = nodes[ i ]; - buildNodeHierachy( nodeId, _scene, dependencies.nodes ); - - } - - _scene.traverse( function ( child ) { - - // for Specular-Glossiness. - if ( child.material && child.material.isGLTFSpecularGlossinessMaterial ) { - - child.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; - - } - - } ); - - // Ambient lighting, if present, is always attached to the scene root. - if ( scene.extensions - && scene.extensions[ EXTENSIONS.KHR_LIGHTS ] - && scene.extensions[ EXTENSIONS.KHR_LIGHTS ].light !== undefined ) { - - var lights = extensions[ EXTENSIONS.KHR_LIGHTS ].lights; - _scene.add( lights[ scene.extensions[ EXTENSIONS.KHR_LIGHTS ].light ] ); - - } - - return _scene; - - } ); - - } ); - - }; - - return GLTF2Loader; - -} )(); diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index 3a076902787d06e6153e6709baccea280d649faf..d40c7a1f1c4aba41261e92d37d21581018e239fb 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -3,6 +3,7 @@ * @author mrdoob / http://mrdoob.com/ * @author Tony Parisi / http://www.tonyparisi.com/ * @author Takahiro / https://github.com/takahirox + * @author Don McCurdy / https://www.donmccurdy.com */ THREE.GLTFLoader = ( function () { @@ -23,7 +24,7 @@ THREE.GLTFLoader = ( function () { var scope = this; - var path = this.path && ( typeof this.path === "string" ) ? this.path : THREE.Loader.prototype.extractUrlBase( url ); + var path = this.path && ( typeof this.path === 'string' ) ? this.path : THREE.Loader.prototype.extractUrlBase( url ); var loader = new THREE.FileLoader( scope.manager ); @@ -31,7 +32,16 @@ THREE.GLTFLoader = ( function () { loader.load( url, function ( data ) { - scope.parse( data, onLoad, path ); + try { + + scope.parse( data, path, onLoad, onError ); + + } catch ( e ) { + + // For SyntaxError or TypeError, return a generic failure message. + onError( e.constructor === Error ? e : new Error( 'THREE.GLTFLoader: Unable to parse model.' ) ); + + } }, onProgress, onError ); @@ -49,14 +59,14 @@ THREE.GLTFLoader = ( function () { }, - parse: function ( data, callback, path ) { + parse: function ( data, path, onLoad, onError ) { var content; var extensions = {}; var magic = convertUint8ArrayToString( new Uint8Array( data, 0, 4 ) ); - if ( magic === BINARY_EXTENSION_HEADER_DEFAULTS.magic ) { + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; @@ -69,9 +79,32 @@ THREE.GLTFLoader = ( function () { var json = JSON.parse( content ); - if ( json.extensionsUsed && json.extensionsUsed.indexOf( EXTENSIONS.KHR_MATERIALS_COMMON ) >= 0 ) { + if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { + + onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); + return; + + } + + if ( json.extensionsUsed ) { + + if( json.extensionsUsed.indexOf( EXTENSIONS.KHR_LIGHTS ) >= 0 ) { + + extensions[ EXTENSIONS.KHR_LIGHTS ] = new GLTFLightsExtension( json ); + + } + + if( json.extensionsUsed.indexOf( EXTENSIONS.KHR_MATERIALS_COMMON ) >= 0 ) { - extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] = new GLTFMaterialsCommonExtension( json ); + extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] = new GLTFMaterialsCommonExtension( json ); + + } + + if( json.extensionsUsed.indexOf( EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ) >= 0 ) { + + extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); + + } } @@ -89,15 +122,15 @@ THREE.GLTFLoader = ( function () { console.timeEnd( 'GLTFLoader' ); var glTF = { - "scene": scene, - "scenes": scenes, - "cameras": cameras, - "animations": animations + scene: scene, + scenes: scenes, + cameras: cameras, + animations: animations }; - callback( glTF ); + onLoad( glTF ); - } ); + }, onError ); } @@ -155,254 +188,579 @@ THREE.GLTFLoader = ( function () { } - /* GLTFSHADERS */ + /*********************************/ + /********** EXTENSIONS ***********/ + /*********************************/ - GLTFLoader.Shaders = { + var EXTENSIONS = { + KHR_BINARY_GLTF: 'KHR_binary_glTF', + KHR_LIGHTS: 'KHR_lights', + KHR_MATERIALS_COMMON: 'KHR_materials_common', + KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness' + }; - update: function () { + /** + * Lights Extension + * + * Specification: PENDING + */ + function GLTFLightsExtension( json ) { - console.warn( 'THREE.GLTFLoader.Shaders has been deprecated, and now updates automatically.' ); + this.name = EXTENSIONS.KHR_LIGHTS; - } + this.lights = {}; - }; + var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS ] ) || {}; + var lights = extension.lights || {}; - /* GLTFSHADER */ + for ( var lightId in lights ) { - function GLTFShader( targetNode, allNodes ) { + var light = lights[ lightId ]; + var lightNode; - var boundUniforms = {}; + var color = new THREE.Color().fromArray( light.color ); - // bind each uniform to its source node + switch ( light.type ) { - var uniforms = targetNode.material.uniforms; + case 'directional': + lightNode = new THREE.DirectionalLight( color ); + lightNode.position.set( 0, 0, 1 ); + break; - for ( var uniformId in uniforms ) { + case 'point': + lightNode = new THREE.PointLight( color ); + break; - var uniform = uniforms[ uniformId ]; + case 'spot': + lightNode = new THREE.SpotLight( color ); + lightNode.position.set( 0, 0, 1 ); + break; - if ( uniform.semantic ) { + case 'ambient': + lightNode = new THREE.AmbientLight( color ); + break; - var sourceNodeRef = uniform.node; + } - var sourceNode = targetNode; + if ( lightNode ) { - if ( sourceNodeRef ) { + if ( light.constantAttenuation !== undefined ) { - sourceNode = allNodes[ sourceNodeRef ]; + lightNode.intensity = light.constantAttenuation; } - boundUniforms[ uniformId ] = { - semantic: uniform.semantic, - sourceNode: sourceNode, - targetNode: targetNode, - uniform: uniform - }; + if ( light.linearAttenuation !== undefined ) { - } + lightNode.distance = 1 / light.linearAttenuation; - } + } - this.boundUniforms = boundUniforms; - this._m4 = new THREE.Matrix4(); + if ( light.quadraticAttenuation !== undefined ) { - } + lightNode.decay = light.quadraticAttenuation; - // Update - update all the uniform values - GLTFShader.prototype.update = function ( scene, camera ) { + } - var boundUniforms = this.boundUniforms; + if ( light.fallOffAngle !== undefined ) { - for ( var name in boundUniforms ) { + lightNode.angle = light.fallOffAngle; - var boundUniform = boundUniforms[ name ]; + } - switch ( boundUniform.semantic ) { + if ( light.fallOffExponent !== undefined ) { - case "MODELVIEW": + console.warn( 'THREE.GLTFLoader:: light.fallOffExponent not currently supported.' ); - var m4 = boundUniform.uniform.value; - m4.multiplyMatrices( camera.matrixWorldInverse, boundUniform.sourceNode.matrixWorld ); - break; + } - case "MODELVIEWINVERSETRANSPOSE": + lightNode.name = light.name || ( 'light_' + lightId ); + this.lights[ lightId ] = lightNode; - var m3 = boundUniform.uniform.value; - this._m4.multiplyMatrices( camera.matrixWorldInverse, boundUniform.sourceNode.matrixWorld ); - m3.getNormalMatrix( this._m4 ); - break; + } - case "PROJECTION": + } - var m4 = boundUniform.uniform.value; - m4.copy( camera.projectionMatrix ); - break; + } - case "JOINTMATRIX": + /** + * Common Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common + */ + function GLTFMaterialsCommonExtension( json ) { - var m4v = boundUniform.uniform.value; + this.name = EXTENSIONS.KHR_MATERIALS_COMMON; - for ( var mi = 0; mi < m4v.length; mi ++ ) { + } - // So it goes like this: - // SkinnedMesh world matrix is already baked into MODELVIEW; - // transform joints to local space, - // then transform using joint's inverse - m4v[ mi ] - .getInverse( boundUniform.sourceNode.matrixWorld ) - .multiply( boundUniform.targetNode.skeleton.bones[ mi ].matrixWorld ) - .multiply( boundUniform.targetNode.skeleton.boneInverses[ mi ] ) - .multiply( boundUniform.targetNode.bindMatrix ); + GLTFMaterialsCommonExtension.prototype.getMaterialType = function ( material ) { - } + var khrMaterial = material.extensions[ this.name ]; - break; + switch ( khrMaterial.type ) { - default : + case 'commonBlinn' : + case 'commonPhong' : + return THREE.MeshPhongMaterial; - console.warn( "Unhandled shader semantic: " + boundUniform.semantic ); - break; + case 'commonLambert' : + return THREE.MeshLambertMaterial; - } + case 'commonConstant' : + default : + return THREE.MeshBasicMaterial; } }; + GLTFMaterialsCommonExtension.prototype.extendParams = function ( materialParams, material, parser ) { - /* ANIMATION */ + var khrMaterial = material.extensions[ this.name ]; - GLTFLoader.Animations = { + var pending = []; - update: function () { + var keys = []; - console.warn( 'THREE.GLTFLoader.Animation has been deprecated. Use THREE.AnimationMixer instead.' ); + // TODO: Currently ignored: 'ambientFactor', 'ambientTexture' + switch ( khrMaterial.type ) { - } + case 'commonBlinn' : + case 'commonPhong' : + keys.push( 'diffuseFactor', 'diffuseTexture', 'specularFactor', 'specularTexture', 'shininessFactor' ); + break; - }; + case 'commonLambert' : + keys.push( 'diffuseFactor', 'diffuseTexture' ); + break; - /*********************************/ - /********** EXTENSIONS ***********/ - /*********************************/ + case 'commonConstant' : + default : + break; - var EXTENSIONS = { - KHR_BINARY_GLTF: 'KHR_binary_glTF', - KHR_MATERIALS_COMMON: 'KHR_materials_common' - }; + } - /* MATERIALS COMMON EXTENSION */ + var materialValues = {}; - function GLTFMaterialsCommonExtension( json ) { + keys.forEach( function( v ) { - this.name = EXTENSIONS.KHR_MATERIALS_COMMON; + if ( khrMaterial[ v ] !== undefined ) materialValues[ v ] = khrMaterial[ v ]; - this.lights = {}; + } ); - var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] ) || {}; - var lights = extension.lights || {}; + if ( materialValues.diffuseFactor !== undefined ) { - for ( var lightId in lights ) { + materialParams.color = new THREE.Color().fromArray( materialValues.diffuseFactor ); + materialParams.opacity = materialValues.diffuseFactor[ 3 ]; - var light = lights[ lightId ]; - var lightNode; + } - var lightParams = light[ light.type ]; - var color = new THREE.Color().fromArray( lightParams.color ); + if ( materialValues.diffuseTexture !== undefined ) { - switch ( light.type ) { + pending.push( parser.assignTexture( materialParams, 'map', materialValues.diffuseTexture.index ) ); - case "directional": - lightNode = new THREE.DirectionalLight( color ); - lightNode.position.set( 0, 0, 1 ); - break; + } - case "point": - lightNode = new THREE.PointLight( color ); - break; + if ( materialValues.specularFactor !== undefined ) { - case "spot": - lightNode = new THREE.SpotLight( color ); - lightNode.position.set( 0, 0, 1 ); - break; + materialParams.specular = new THREE.Color().fromArray( materialValues.specularFactor ); - case "ambient": - lightNode = new THREE.AmbientLight( color ); - break; + } - } + if ( materialValues.specularTexture !== undefined ) { - if ( lightNode ) { + pending.push( parser.assignTexture( materialParams, 'specularMap', materialValues.specularTexture.index ) ); - this.lights[ lightId ] = lightNode; + } - } + if ( materialValues.shininessFactor !== undefined ) { + + materialParams.shininess = materialValues.shininessFactor; } - } + return Promise.all( pending ); + + }; /* BINARY EXTENSION */ var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF'; - - var BINARY_EXTENSION_HEADER_DEFAULTS = { magic: 'glTF', version: 1, contentFormat: 0 }; - - var BINARY_EXTENSION_HEADER_LENGTH = 20; + var BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; + var BINARY_EXTENSION_HEADER_LENGTH = 12; + var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; function GLTFBinaryExtension( data ) { this.name = EXTENSIONS.KHR_BINARY_GLTF; + this.content = null; + this.body = null; var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); - var header = { + this.header = { magic: convertUint8ArrayToString( new Uint8Array( data.slice( 0, 4 ) ) ), version: headerView.getUint32( 4, true ), - length: headerView.getUint32( 8, true ), - contentLength: headerView.getUint32( 12, true ), - contentFormat: headerView.getUint32( 16, true ) + length: headerView.getUint32( 8, true ) }; - for ( var key in BINARY_EXTENSION_HEADER_DEFAULTS ) { + if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { - var value = BINARY_EXTENSION_HEADER_DEFAULTS[ key ]; + throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); - if ( header[ key ] !== value ) { + } else if ( this.header.version < 2.0 ) { - throw new Error( 'Unsupported glTF-Binary header: Expected "%s" to be "%s".', key, value ); + throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use GLTFLoader instead.' ); + + } + + var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); + var chunkIndex = 0; + + while ( chunkIndex < chunkView.byteLength ) { + + var chunkLength = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + var chunkType = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { + + var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); + this.content = convertUint8ArrayToString( contentArray ); + + } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { + + var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; + this.body = data.slice( byteOffset, byteOffset + chunkLength ); } + // Clients must ignore chunks with unknown types. + + chunkIndex += chunkLength; + } - var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH, header.contentLength ); + if ( this.content === null ) { + + throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); - this.header = header; - this.content = convertUint8ArrayToString( contentArray ); - this.body = data.slice( BINARY_EXTENSION_HEADER_LENGTH + header.contentLength, header.length ); + } } - GLTFBinaryExtension.prototype.loadShader = function ( shader, bufferViews ) { + /** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness + */ + function GLTFMaterialsPbrSpecularGlossinessExtension() { - var bufferView = bufferViews[ shader.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].bufferView ]; - var array = new Uint8Array( bufferView ); + return { - return convertUint8ArrayToString( array ); + name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, - }; + getMaterialType: function () { - GLTFBinaryExtension.prototype.loadTextureSourceUri = function ( source, bufferViews ) { + return THREE.ShaderMaterial; - var metadata = source.extensions[ EXTENSIONS.KHR_BINARY_GLTF ]; - var bufferView = bufferViews[ metadata.bufferView ]; - var stringData = convertUint8ArrayToString( new Uint8Array( bufferView ) ); + }, - return 'data:' + metadata.mimeType + ';base64,' + btoa( stringData ); + extendParams: function ( params, material, parser ) { + + var pbrSpecularGlossiness = material.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 );', + ' // 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 fragmentShader = shader.fragmentShader + .replace( '#include ', '' ) + .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 }; + + params.vertexShader = shader.vertexShader; + params.fragmentShader = fragmentShader; + params.uniforms = uniforms; + params.defines = { 'STANDARD': '' }; + + params.color = new THREE.Color( 1.0, 1.0, 1.0 ); + params.opacity = 1.0; + + var pending = []; + + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + + var array = pbrSpecularGlossiness.diffuseFactor; + + params.color.fromArray( array ); + params.opacity = array[ 3 ]; - }; + } + + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + + pending.push( parser.assignTexture( params, 'map', pbrSpecularGlossiness.diffuseTexture.index ) ); + + } + + params.emissive = new THREE.Color( 0.0, 0.0, 0.0 ); + params.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; + params.specular = new THREE.Color( 1.0, 1.0, 1.0 ); + + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { + + params.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + + } + + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + + var specGlossIndex = pbrSpecularGlossiness.specularGlossinessTexture.index; + pending.push( parser.assignTexture( params, 'glossinessMap', specGlossIndex ) ); + pending.push( parser.assignTexture( params, 'specularMap', specGlossIndex ) ); + + } + + return Promise.all( pending ); + + }, + + createMaterial: function ( params ) { + + // setup material properties based on MeshStandardMaterial for Specular-Glossiness + + 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; + + material.color = params.color; + + material.map = params.map === undefined ? null : params.map; + + material.lightMap = null; + material.lightMapIntensity = 1.0; + + material.aoMap = params.aoMap === undefined ? null : params.aoMap; + material.aoMapIntensity = 1.0; + + material.emissive = params.emissive; + material.emissiveIntensity = 1.0; + material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap; + + material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap; + material.bumpScale = 1; + + material.normalMap = params.normalMap === undefined ? null : params.normalMap; + material.normalScale = new THREE.Vector2( 1, 1 ); + + material.displacementMap = null; + material.displacementScale = 1; + material.displacementBias = 0; + + material.specularMap = params.specularMap === undefined ? null : params.specularMap; + material.specular = params.specular; + + 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; + + material.refractionRatio = 0.98; + + material.extensions.derivatives = true; + + return material; + + }, + + // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. + refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) { + + 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 ); + + 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; + + // uv repeat and offset setting priorities + // 1. color map + // 2. specular map + // 3. normal map + // 4. bump map + // 5. alpha map + // 6. emissive map + + var uvScaleMap; + + if ( material.map ) { + + uvScaleMap = material.map; + + } else if ( material.specularMap ) { + + uvScaleMap = material.specularMap; + + } else if ( material.displacementMap ) { + + uvScaleMap = material.displacementMap; + + } else if ( material.normalMap ) { + + uvScaleMap = material.normalMap; + + } else if ( material.bumpMap ) { + + uvScaleMap = material.bumpMap; + + } else if ( material.glossinessMap ) { + + uvScaleMap = material.glossinessMap; + + } else if ( material.alphaMap ) { + + uvScaleMap = material.alphaMap; + + } else if ( material.emissiveMap ) { + + uvScaleMap = material.emissiveMap; + + } + + if ( uvScaleMap !== undefined ) { + + // backwards compatibility + if ( uvScaleMap.isWebGLRenderTarget ) { + + uvScaleMap = uvScaleMap.texture; + + } + + var offset = uvScaleMap.offset; + var repeat = uvScaleMap.repeat; + + uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); + + } + + uniforms.envMap.value = material.envMap; + uniforms.envMapIntensity.value = material.envMapIntensity; + uniforms.flipEnvMap.value = ( material.envMap && material.envMap.isCubeTexture ) ? -1 : 1; + + uniforms.refractionRatio.value = material.refractionRatio; + + uniforms.specular.value.copy( material.specular ); + uniforms.glossiness.value = material.glossiness; + + uniforms.glossinessMap.value = material.glossinessMap; + + uniforms.emissiveMap.value = material.emissiveMap; + uniforms.bumpMap.value = material.bumpMap; + uniforms.normalMap.value = material.normalMap; + + uniforms.displacementMap.value = material.displacementMap; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; + + if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { + + defines.USE_GLOSSINESSMAP = ''; + // set USE_ROUGHNESSMAP to enable vUv + defines.USE_ROUGHNESSMAP = ''; + + } + + if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { + + delete defines.USE_GLOSSINESSMAP; + delete defines.USE_ROUGHNESSMAP; + + } + + } + + }; + + } /*********************************/ /********** INTERNALS ************/ @@ -421,13 +779,15 @@ THREE.GLTFLoader = ( function () { LINEAR: 9729, REPEAT: 10497, SAMPLER_2D: 35678, - TRIANGLES: 4, + POINTS: 0, LINES: 1, + LINE_LOOP: 2, + LINE_STRIP: 3, + TRIANGLES: 4, + TRIANGLE_STRIP: 5, + TRIANGLE_FAN: 6, UNSIGNED_BYTE: 5121, - UNSIGNED_SHORT: 5123, - - VERTEX_SHADER: 35633, - FRAGMENT_SHADER: 35632 + UNSIGNED_SHORT: 5123 }; var WEBGL_TYPE = { @@ -535,10 +895,13 @@ THREE.GLTFLoader = ( function () { var PATH_PROPERTIES = { scale: 'scale', translation: 'position', - rotation: 'quaternion' + rotation: 'quaternion', + weights: 'morphTargetInfluences' }; var INTERPOLATION = { + CATMULLROMSPLINE: THREE.InterpolateSmooth, + CUBICSPLINE: THREE.InterpolateSmooth, LINEAR: THREE.InterpolateLinear, STEP: THREE.InterpolateDiscrete }; @@ -552,6 +915,12 @@ THREE.GLTFLoader = ( function () { 32926: 'SAMPLE_ALPHA_TO_COVERAGE' }; + var ALPHA_MODES = { + OPAQUE: 'OPAQUE', + MASK: 'MASK', + BLEND: 'BLEND' + }; + /* UTILITY FUNCTIONS */ function _each( object, callback, thisObj ) { @@ -659,6 +1028,13 @@ THREE.GLTFLoader = ( function () { } + // Blob URL + if ( /^blob:.*$/i.test( url ) ) { + + return url; + + } + // Relative URL return ( path || '' ) + url; @@ -687,151 +1063,141 @@ THREE.GLTFLoader = ( function () { } - // Three.js seems too dependent on attribute names so globally - // replace those in the shader code - function replaceTHREEShaderAttributes( shaderText, technique ) { - - // Expected technique attributes - var attributes = {}; - - for ( var attributeId in technique.attributes ) { - - var pname = technique.attributes[ attributeId ]; + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material + */ + function createDefaultMaterial() { - var param = technique.parameters[ pname ]; - var atype = param.type; - var semantic = param.semantic; + return new THREE.MeshStandardMaterial( { + color: 0xFFFFFF, + emissive: 0x000000, + metalness: 1, + roughness: 1, + transparent: false, + depthTest: true, + side: THREE.FrontSide + } ); - attributes[ attributeId ] = { - type: atype, - semantic: semantic - }; + } - } + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets + * @param {THREE.Mesh} mesh + * @param {GLTF.Mesh} meshDef + * @param {GLTF.Primitive} primitiveDef + * @param {Object} dependencies + */ + function addMorphTargets ( mesh, meshDef, primitiveDef, dependencies ) { - // Figure out which attributes to change in technique + var geometry = mesh.geometry; + var material = mesh.material; - var shaderParams = technique.parameters; - var shaderAttributes = technique.attributes; - var params = {}; + var targets = primitiveDef.targets; + var morphAttributes = geometry.morphAttributes; - for ( var attributeId in attributes ) { + morphAttributes.position = []; + morphAttributes.normal = []; - var pname = shaderAttributes[ attributeId ]; - var shaderParam = shaderParams[ pname ]; - var semantic = shaderParam.semantic; - if ( semantic ) { + material.morphTargets = true; - params[ attributeId ] = shaderParam; + for ( var i = 0, il = targets.length; i < il; i ++ ) { - } + var target = targets[ i ]; + var attributeName = 'morphTarget' + i; - } + var positionAttribute, normalAttribute; - for ( var pname in params ) { + if ( target.POSITION !== undefined ) { - var param = params[ pname ]; - var semantic = param.semantic; + // Three.js morph formula is + // position + // + weight0 * ( morphTarget0 - position ) + // + weight1 * ( morphTarget1 - position ) + // ... + // while the glTF one is + // position + // + weight0 * morphTarget0 + // + weight1 * morphTarget1 + // ... + // then adding position to morphTarget. + // So morphTarget value will depend on mesh's position, then cloning attribute + // for the case if attribute is shared among two or more meshes. - var regEx = new RegExp( "\\b" + pname + "\\b", "g" ); + positionAttribute = dependencies.accessors[ target.POSITION ].clone(); + var position = geometry.attributes.position; - switch ( semantic ) { + for ( var j = 0, jl = positionAttribute.count; j < jl; j ++ ) { - case "POSITION": + positionAttribute.setXYZ( + j, + positionAttribute.getX( j ) + position.getX( j ), + positionAttribute.getY( j ) + position.getY( j ), + positionAttribute.getZ( j ) + position.getZ( j ) + ); - shaderText = shaderText.replace( regEx, 'position' ); - break; + } - case "NORMAL": + } else { - shaderText = shaderText.replace( regEx, 'normal' ); - break; + // Copying the original position not to affect the final position. + // See the formula above. + positionAttribute = geometry.attributes.position.clone(); - case 'TEXCOORD_0': - case 'TEXCOORD0': - case 'TEXCOORD': + } - shaderText = shaderText.replace( regEx, 'uv' ); - break; + if ( target.NORMAL !== undefined ) { - case 'TEXCOORD_1': + material.morphNormals = true; - shaderText = shaderText.replace( regEx, 'uv2' ); - break; + // see target.POSITION's comment - case 'COLOR_0': - case 'COLOR0': - case 'COLOR': + normalAttribute = dependencies.accessors[ target.NORMAL ].clone(); + var normal = geometry.attributes.normal; - shaderText = shaderText.replace( regEx, 'color' ); - break; + for ( var j = 0, jl = normalAttribute.count; j < jl; j ++ ) { - case "WEIGHT": + normalAttribute.setXYZ( + j, + normalAttribute.getX( j ) + normal.getX( j ), + normalAttribute.getY( j ) + normal.getY( j ), + normalAttribute.getZ( j ) + normal.getZ( j ) + ); - shaderText = shaderText.replace( regEx, 'skinWeight' ); - break; + } - case "JOINT": + } else { - shaderText = shaderText.replace( regEx, 'skinIndex' ); - break; + normalAttribute = geometry.attributes.normal.clone(); } - } - - return shaderText; - - } - - function createDefaultMaterial() { - - return new THREE.MeshPhongMaterial( { - color: 0x00000, - emissive: 0x888888, - specular: 0x000000, - shininess: 0, - transparent: false, - depthTest: true, - side: THREE.FrontSide - } ); - - } - - // Deferred constructor for RawShaderMaterial types - function DeferredShaderMaterial( params ) { + if ( target.TANGENT !== undefined ) { - this.isDeferredShaderMaterial = true; + // TODO: implement - this.params = params; + } - } + positionAttribute.name = attributeName; + normalAttribute.name = attributeName; - DeferredShaderMaterial.prototype.create = function () { + morphAttributes.position.push( positionAttribute ); + morphAttributes.normal.push( normalAttribute ); - var uniforms = THREE.UniformsUtils.clone( this.params.uniforms ); + } - for ( var uniformId in this.params.uniforms ) { + mesh.updateMorphTargets(); - var originalUniform = this.params.uniforms[ uniformId ]; + if ( meshDef.weights !== undefined ) { - if ( originalUniform.value instanceof THREE.Texture ) { + for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { - uniforms[ uniformId ].value = originalUniform.value; - uniforms[ uniformId ].value.needsUpdate = true; + mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; } - uniforms[ uniformId ].semantic = originalUniform.semantic; - uniforms[ uniformId ].node = originalUniform.node; - } - this.params.uniforms = uniforms; - - return new THREE.RawShaderMaterial( this.params ); - - }; + } /* GLTF PARSER */ @@ -853,7 +1219,7 @@ THREE.GLTFLoader = ( function () { for ( var i = 0; i < dependencies.length; i ++ ) { var dependency = dependencies[ i ]; - var fnName = "load" + dependency.charAt( 0 ).toUpperCase() + dependency.slice( 1 ); + var fnName = 'load' + dependency.charAt( 0 ).toUpperCase() + dependency.slice( 1 ); var cached = this.cache.get( dependency ); @@ -880,7 +1246,7 @@ THREE.GLTFLoader = ( function () { }; - GLTFParser.prototype.parse = function ( callback ) { + GLTFParser.prototype.parse = function ( onLoad, onError ) { var json = this.json; @@ -890,9 +1256,9 @@ THREE.GLTFLoader = ( function () { // Fire the callback on complete this._withDependencies( [ - "scenes", - "cameras", - "animations" + 'scenes', + 'cameras', + 'animations' ] ).then( function ( dependencies ) { @@ -923,661 +1289,472 @@ THREE.GLTFLoader = ( function () { } - callback( scene, scenes, cameras, animations ); + onLoad( scene, scenes, cameras, animations ); - } ); + } ).catch( onError ); }; - GLTFParser.prototype.loadShaders = function () { - - var json = this.json; - var extensions = this.extensions; - var options = this.options; - - return this._withDependencies( [ - - "bufferViews" - - ] ).then( function ( dependencies ) { - - return _each( json.shaders, function ( shader ) { - - if ( shader.extensions && shader.extensions[ EXTENSIONS.KHR_BINARY_GLTF ] ) { - - return extensions[ EXTENSIONS.KHR_BINARY_GLTF ].loadShader( shader, dependencies.bufferViews ); - - } - - return new Promise( function ( resolve ) { - - var loader = new THREE.FileLoader(); - loader.setResponseType( 'text' ); - loader.load( resolveURL( shader.uri, options.path ), function ( shaderText ) { - - resolve( shaderText ); - - } ); - - } ); - - } ); - - } ); - - }; + /** + * Requests the specified dependency asynchronously, with caching. + * @param {string} type + * @param {number} index + * @return {Promise} + */ + GLTFParser.prototype.getDependency = function ( type, index ) { - GLTFParser.prototype.loadBuffers = function () { + var cacheKey = type + ':' + index; + var dependency = this.cache.get( cacheKey ); - var json = this.json; - var extensions = this.extensions; - var options = this.options; - - return _each( json.buffers, function ( buffer, name ) { - - if ( name === BINARY_EXTENSION_BUFFER_NAME ) { - - return extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body; - - } - - if ( buffer.type === 'arraybuffer' || buffer.type === undefined ) { - - return new Promise( function ( resolve ) { - - var loader = new THREE.FileLoader(); - loader.setResponseType( 'arraybuffer' ); - loader.load( resolveURL( buffer.uri, options.path ), function ( buffer ) { - - resolve( buffer ); - - } ); - - } ); - - } else { - - console.warn( 'THREE.GLTFLoader: ' + buffer.type + ' buffer type is not supported' ); - - } - - } ); - - }; - - GLTFParser.prototype.loadBufferViews = function () { - - var json = this.json; - - return this._withDependencies( [ - - "buffers" - - ] ).then( function ( dependencies ) { - - return _each( json.bufferViews, function ( bufferView ) { - - var arraybuffer = dependencies.buffers[ bufferView.buffer ]; - - var byteLength = bufferView.byteLength !== undefined ? bufferView.byteLength : 0; - - return arraybuffer.slice( bufferView.byteOffset, bufferView.byteOffset + byteLength ); - - } ); - - } ); - - }; - - GLTFParser.prototype.loadAccessors = function () { - - var json = this.json; - - return this._withDependencies( [ - - "bufferViews" - - ] ).then( function ( dependencies ) { - - return _each( json.accessors, function ( accessor ) { - - var arraybuffer = dependencies.bufferViews[ accessor.bufferView ]; - var itemSize = WEBGL_TYPE_SIZES[ accessor.type ]; - var TypedArray = WEBGL_COMPONENT_TYPES[ accessor.componentType ]; - - // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. - var elementBytes = TypedArray.BYTES_PER_ELEMENT; - var itemBytes = elementBytes * itemSize; - - // The buffer is not interleaved if the stride is the item size in bytes. - if ( accessor.byteStride && accessor.byteStride !== itemBytes ) { - - // Use the full buffer if it's interleaved. - var array = new TypedArray( arraybuffer ); - - // Integer parameters to IB/IBA are in array elements, not bytes. - var ib = new THREE.InterleavedBuffer( array, accessor.byteStride / elementBytes ); - - return new THREE.InterleavedBufferAttribute( ib, itemSize, accessor.byteOffset / elementBytes ); - - } else { - - array = new TypedArray( arraybuffer, accessor.byteOffset, accessor.count * itemSize ); - - return new THREE.BufferAttribute( array, itemSize ); - - } - - } ); - - } ); - - }; - - GLTFParser.prototype.loadTextures = function () { - - var json = this.json; - var extensions = this.extensions; - var options = this.options; - - return this._withDependencies( [ - - "bufferViews" - - ] ).then( function ( dependencies ) { - - return _each( json.textures, function ( texture ) { - - if ( texture.source ) { - - return new Promise( function ( resolve ) { - - var source = json.images[ texture.source ]; - var sourceUri = source.uri; - - if ( source.extensions && source.extensions[ EXTENSIONS.KHR_BINARY_GLTF ] ) { - - sourceUri = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].loadTextureSourceUri( source, dependencies.bufferViews ); - - } - - var textureLoader = THREE.Loader.Handlers.get( sourceUri ); - - if ( textureLoader === null ) { - - textureLoader = new THREE.TextureLoader(); - - } - - textureLoader.setCrossOrigin( options.crossOrigin ); - - textureLoader.load( resolveURL( sourceUri, options.path ), function ( _texture ) { - - _texture.flipY = false; - - if ( texture.name !== undefined ) _texture.name = texture.name; - - _texture.format = texture.format !== undefined ? WEBGL_TEXTURE_FORMATS[ texture.format ] : THREE.RGBAFormat; - - if ( texture.internalFormat !== undefined && _texture.format !== WEBGL_TEXTURE_FORMATS[ texture.internalFormat ] ) { - - console.warn( 'THREE.GLTFLoader: Three.js doesn\'t support texture internalFormat which is different from texture format. ' + - 'internalFormat will be forced to be the same value as format.' ); - - } - - _texture.type = texture.type !== undefined ? WEBGL_TEXTURE_DATATYPES[ texture.type ] : THREE.UnsignedByteType; - - if ( texture.sampler ) { - - var sampler = json.samplers[ texture.sampler ]; - - _texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter; - _texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.NearestMipMapLinearFilter; - _texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping; - _texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping; - - } - - resolve( _texture ); - - }, undefined, function () { - - resolve(); - - } ); - - } ); - - } - - } ); - - } ); - - }; - - GLTFParser.prototype.loadMaterials = function () { - - var json = this.json; - - return this._withDependencies( [ + if ( !dependency ) { - "shaders", - "textures" + var fnName = 'load' + type.charAt( 0 ).toUpperCase() + type.slice( 1 ); + dependency = this[ fnName ]( index ); + this.cache.add( cacheKey, dependency ); - ] ).then( function ( dependencies ) { + } - return _each( json.materials, function ( material ) { + return dependency; - var materialType; - var materialValues = {}; - var materialParams = {}; + }; - var khr_material; + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferIndex + * @return {Promise} + */ + GLTFParser.prototype.loadBuffer = function ( bufferIndex ) { - if ( material.extensions && material.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] ) { + var bufferDef = this.json.buffers[ bufferIndex ]; - khr_material = material.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ]; + if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { - } + throw new Error( 'THREE.GLTFLoader: %s buffer type is not supported.', bufferDef.type ); - if ( khr_material ) { + } - // don't copy over unused values to avoid material warning spam - var keys = [ 'ambient', 'emission', 'transparent', 'transparency', 'doubleSided' ]; + // If present, GLB container is required to be the first buffer. + if ( bufferDef.uri === undefined && bufferIndex === 0 ) { - switch ( khr_material.technique ) { + return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); - case 'BLINN' : - case 'PHONG' : - materialType = THREE.MeshPhongMaterial; - keys.push( 'diffuse', 'specular', 'shininess' ); - break; + } - case 'LAMBERT' : - materialType = THREE.MeshLambertMaterial; - keys.push( 'diffuse' ); - break; + var options = this.options; - case 'CONSTANT' : - default : - materialType = THREE.MeshBasicMaterial; - break; + return new Promise( function ( resolve ) { - } + var loader = new THREE.FileLoader(); + loader.setResponseType( 'arraybuffer' ); + loader.load( resolveURL( bufferDef.uri, options.path ), resolve); - keys.forEach( function( v ) { + } ); - if ( khr_material.values[ v ] !== undefined ) materialValues[ v ] = khr_material.values[ v ]; + }; - } ); + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferViewIndex + * @return {Promise} + */ + GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) { - if ( khr_material.doubleSided || materialValues.doubleSided ) { + var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; - materialParams.side = THREE.DoubleSide; + return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { - } + var byteLength = bufferViewDef.byteLength || 0; + var byteOffset = bufferViewDef.byteOffset || 0; + return buffer.slice( byteOffset, byteOffset + byteLength ); - if ( khr_material.transparent || materialValues.transparent ) { + } ); - materialParams.transparent = true; - materialParams.opacity = ( materialValues.transparency !== undefined ) ? materialValues.transparency : 1; + }; - } + GLTFParser.prototype.loadAccessors = function () { - } else if ( material.technique === undefined ) { + var parser = this; + var json = this.json; - materialType = THREE.MeshPhongMaterial; + return _each( json.accessors, function ( accessor ) { - Object.assign( materialValues, material.values ); + return parser.getDependency( 'bufferView', accessor.bufferView ).then( function ( bufferView ) { - } else { + var itemSize = WEBGL_TYPE_SIZES[ accessor.type ]; + var TypedArray = WEBGL_COMPONENT_TYPES[ accessor.componentType ]; - materialType = DeferredShaderMaterial; + // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. + var elementBytes = TypedArray.BYTES_PER_ELEMENT; + var itemBytes = elementBytes * itemSize; + var byteStride = json.bufferViews[ accessor.bufferView ].byteStride; + var array; - var technique = json.techniques[ material.technique ]; + // The buffer is not interleaved if the stride is the item size in bytes. + if ( byteStride && byteStride !== itemBytes ) { - materialParams.uniforms = {}; + // Use the full buffer if it's interleaved. + array = new TypedArray( bufferView ); - var program = json.programs[ technique.program ]; + // Integer parameters to IB/IBA are in array elements, not bytes. + var ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes ); - if ( program ) { + return new THREE.InterleavedBufferAttribute( ib, itemSize, accessor.byteOffset / elementBytes ); - materialParams.fragmentShader = dependencies.shaders[ program.fragmentShader ]; + } else { - if ( ! materialParams.fragmentShader ) { + array = new TypedArray( bufferView, accessor.byteOffset, accessor.count * itemSize ); - console.warn( "ERROR: Missing fragment shader definition:", program.fragmentShader ); - materialType = THREE.MeshPhongMaterial; + return new THREE.BufferAttribute( array, itemSize ); - } + } - var vertexShader = dependencies.shaders[ program.vertexShader ]; + } ); - if ( ! vertexShader ) { + } ); - console.warn( "ERROR: Missing vertex shader definition:", program.vertexShader ); - materialType = THREE.MeshPhongMaterial; + }; - } + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures + * @param {number} textureIndex + * @return {Promise} + */ + GLTFParser.prototype.loadTexture = function ( textureIndex ) { - // IMPORTANT: FIX VERTEX SHADER ATTRIBUTE DEFINITIONS - materialParams.vertexShader = replaceTHREEShaderAttributes( vertexShader, technique ); + var parser = this; + var json = this.json; + var options = this.options; - var uniforms = technique.uniforms; + var URL = window.URL || window.webkitURL; - for ( var uniformId in uniforms ) { + var textureDef = json.textures[ textureIndex ]; + var source = json.images[ textureDef.source ]; + var sourceURI = source.uri; + var isObjectURL = false; - var pname = uniforms[ uniformId ]; - var shaderParam = technique.parameters[ pname ]; + if ( source.bufferView !== undefined ) { - var ptype = shaderParam.type; + // Load binary image data from bufferView, if provided. - if ( WEBGL_TYPE[ ptype ] ) { + sourceURI = parser.getDependency( 'bufferView', source.bufferView ) + .then( function ( bufferView ) { - var pcount = shaderParam.count; - var value; + isObjectURL = true; + var blob = new Blob( [ bufferView ], { type: source.mimeType } ); + sourceURI = URL.createObjectURL( blob ); + return sourceURI; - if ( material.values !== undefined ) value = material.values[ pname ]; + } ); - var uvalue = new WEBGL_TYPE[ ptype ](); - var usemantic = shaderParam.semantic; - var unode = shaderParam.node; + } - switch ( ptype ) { + return Promise.resolve( sourceURI ).then( function ( sourceURI ) { - case WEBGL_CONSTANTS.FLOAT: + // Load Texture resource. - uvalue = shaderParam.value; + var textureLoader = THREE.Loader.Handlers.get( sourceURI ) || new THREE.TextureLoader(); + textureLoader.setCrossOrigin( options.crossOrigin ); - if ( pname == "transparency" ) { + return new Promise( function ( resolve, reject ) { - materialParams.transparent = true; + textureLoader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); - } + } ); - if ( value !== undefined ) { + } ).then( function ( texture ) { - uvalue = value; + // Clean up resources and configure Texture. - } + if ( isObjectURL !== undefined ) { - break; + URL.revokeObjectURL( sourceURI ); - case WEBGL_CONSTANTS.FLOAT_VEC2: - case WEBGL_CONSTANTS.FLOAT_VEC3: - case WEBGL_CONSTANTS.FLOAT_VEC4: - case WEBGL_CONSTANTS.FLOAT_MAT3: + } - if ( shaderParam && shaderParam.value ) { + texture.flipY = false; - uvalue.fromArray( shaderParam.value ); + if ( textureDef.name !== undefined ) texture.name = textureDef.name; - } + texture.format = textureDef.format !== undefined ? WEBGL_TEXTURE_FORMATS[ textureDef.format ] : THREE.RGBAFormat; - if ( value ) { + if ( textureDef.internalFormat !== undefined && texture.format !== WEBGL_TEXTURE_FORMATS[ textureDef.internalFormat ] ) { - uvalue.fromArray( value ); + console.warn( 'THREE.GLTFLoader: Three.js does not support texture internalFormat which is different from texture format. ' + + 'internalFormat will be forced to be the same value as format.' ); - } + } - break; + texture.type = textureDef.type !== undefined ? WEBGL_TEXTURE_DATATYPES[ textureDef.type ] : THREE.UnsignedByteType; - case WEBGL_CONSTANTS.FLOAT_MAT2: + var samplers = json.samplers || {}; + var sampler = samplers[ textureDef.sampler ] || {}; - // what to do? - console.warn( "FLOAT_MAT2 is not a supported uniform type" ); - break; + texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter; + texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipMapLinearFilter; + texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping; + texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping; - case WEBGL_CONSTANTS.FLOAT_MAT4: + return texture; - if ( pcount ) { + } ); - uvalue = new Array( pcount ); + }; - for ( var mi = 0; mi < pcount; mi ++ ) { + /** + * Asynchronously assigns a texture to the given material parameters. + * @param {Object} materialParams + * @param {string} textureName + * @param {number} textureIndex + * @return {Promise} + */ + GLTFParser.prototype.assignTexture = function ( materialParams, textureName, textureIndex ) { - uvalue[ mi ] = new WEBGL_TYPE[ ptype ](); + return this.getDependency( 'texture', textureIndex ).then( function ( texture ) { - } + materialParams[ textureName ] = texture; - if ( shaderParam && shaderParam.value ) { + } ); - var m4v = shaderParam.value; - uvalue.fromArray( m4v ); + }; - } + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials + * @return {Promise>} + */ + GLTFParser.prototype.loadMaterials = function () { - if ( value ) { + var parser = this; + var json = this.json; + var extensions = this.extensions; - uvalue.fromArray( value ); + return _each( json.materials, function ( material ) { - } + var materialType; + var materialParams = {}; + var materialExtensions = material.extensions || {}; - } else { + var pending = []; - if ( shaderParam && shaderParam.value ) { + if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] ) { - var m4 = shaderParam.value; - uvalue.fromArray( m4 ); + var khcExtension = extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ]; + materialType = khcExtension.getMaterialType( material ); + pending.push( khcExtension.extendParams( materialParams, material, parser ) ); - } + } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { - if ( value ) { + var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; + materialType = sgExtension.getMaterialType( material ); + pending.push( sgExtension.extendParams( materialParams, material, parser ) ); - uvalue.fromArray( value ); + } else if ( material.pbrMetallicRoughness !== undefined ) { - } + // Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material - } + materialType = THREE.MeshStandardMaterial; - break; + var metallicRoughness = material.pbrMetallicRoughness; - case WEBGL_CONSTANTS.SAMPLER_2D: + materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; - if ( value !== undefined ) { + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - uvalue = dependencies.textures[ value ]; + var array = metallicRoughness.baseColorFactor; - } else if ( shaderParam.value !== undefined ) { + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; - uvalue = dependencies.textures[ shaderParam.value ]; + } - } else { + if ( metallicRoughness.baseColorTexture !== undefined ) { - uvalue = null; + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture.index ) ); - } + } - break; + materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; + materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; - } + if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { - materialParams.uniforms[ uniformId ] = { - value: uvalue, - semantic: usemantic, - node: unode - }; + var textureIndex = metallicRoughness.metallicRoughnessTexture.index; + pending.push( parser.assignTexture( materialParams, 'metalnessMap', textureIndex ) ); + pending.push( parser.assignTexture( materialParams, 'roughnessMap', textureIndex ) ); - } else { + } - throw new Error( "Unknown shader uniform param type: " + ptype ); + } else { - } + materialType = THREE.MeshPhongMaterial; - } + } - var states = technique.states || {}; - var enables = states.enable || []; - var functions = states.functions || {}; + if ( material.doubleSided === true ) { - var enableCullFace = false; - var enableDepthTest = false; - var enableBlend = false; + materialParams.side = THREE.DoubleSide; - for ( var i = 0, il = enables.length; i < il; i ++ ) { + } - var enable = enables[ i ]; + var alphaMode = material.alphaMode || ALPHA_MODES.OPAQUE; - switch ( STATES_ENABLES[ enable ] ) { + if ( alphaMode !== ALPHA_MODES.OPAQUE ) { - case 'CULL_FACE': + materialParams.transparent = true; - enableCullFace = true; + } else { - break; + materialParams.transparent = false; - case 'DEPTH_TEST': + } - enableDepthTest = true; + if ( material.normalTexture !== undefined ) { - break; + pending.push( parser.assignTexture( materialParams, 'normalMap', material.normalTexture.index ) ); - case 'BLEND': + } - enableBlend = true; + if ( material.occlusionTexture !== undefined ) { - break; + pending.push( parser.assignTexture( materialParams, 'aoMap', material.occlusionTexture.index ) ); - // TODO: implement - case 'SCISSOR_TEST': - case 'POLYGON_OFFSET_FILL': - case 'SAMPLE_ALPHA_TO_COVERAGE': + } - break; + if ( material.emissiveFactor !== undefined ) { - default: + if ( materialType === THREE.MeshBasicMaterial ) { - throw new Error( "Unknown technique.states.enable: " + enable ); + materialParams.color = new THREE.Color().fromArray( material.emissiveFactor ); - } + } else { - } + materialParams.emissive = new THREE.Color().fromArray( material.emissiveFactor ); - if ( enableCullFace ) { + } - materialParams.side = functions.cullFace !== undefined ? WEBGL_SIDES[ functions.cullFace ] : THREE.FrontSide; + } - } else { + if ( material.emissiveTexture !== undefined ) { - materialParams.side = THREE.DoubleSide; + if ( materialType === THREE.MeshBasicMaterial ) { - } + pending.push( parser.assignTexture( materialParams, 'map', material.emissiveTexture.index ) ); - materialParams.depthTest = enableDepthTest; - materialParams.depthFunc = functions.depthFunc !== undefined ? WEBGL_DEPTH_FUNCS[ functions.depthFunc ] : THREE.LessDepth; - materialParams.depthWrite = functions.depthMask !== undefined ? functions.depthMask[ 0 ] : true; + } else { - materialParams.blending = enableBlend ? THREE.CustomBlending : THREE.NoBlending; - materialParams.transparent = enableBlend; + pending.push( parser.assignTexture( materialParams, 'emissiveMap', material.emissiveTexture.index ) ); - var blendEquationSeparate = functions.blendEquationSeparate; + } - if ( blendEquationSeparate !== undefined ) { + } - materialParams.blendEquation = WEBGL_BLEND_EQUATIONS[ blendEquationSeparate[ 0 ] ]; - materialParams.blendEquationAlpha = WEBGL_BLEND_EQUATIONS[ blendEquationSeparate[ 1 ] ]; + return Promise.all( pending ).then( function () { - } else { + var _material; - materialParams.blendEquation = THREE.AddEquation; - materialParams.blendEquationAlpha = THREE.AddEquation; + if ( materialType === THREE.ShaderMaterial ) { - } + _material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); - var blendFuncSeparate = functions.blendFuncSeparate; + } else { - if ( blendFuncSeparate !== undefined ) { + _material = new materialType( materialParams ); - materialParams.blendSrc = WEBGL_BLEND_FUNCS[ blendFuncSeparate[ 0 ] ]; - materialParams.blendDst = WEBGL_BLEND_FUNCS[ blendFuncSeparate[ 1 ] ]; - materialParams.blendSrcAlpha = WEBGL_BLEND_FUNCS[ blendFuncSeparate[ 2 ] ]; - materialParams.blendDstAlpha = WEBGL_BLEND_FUNCS[ blendFuncSeparate[ 3 ] ]; + } - } else { + if ( material.name !== undefined ) _material.name = material.name; - materialParams.blendSrc = THREE.OneFactor; - materialParams.blendDst = THREE.ZeroFactor; - materialParams.blendSrcAlpha = THREE.OneFactor; - materialParams.blendDstAlpha = THREE.ZeroFactor; + // Normal map textures use OpenGL conventions: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materialnormaltexture + _material.normalScale.x = -1; - } + _material.userData = material.extras; - } + return _material; - } + } ); - if ( Array.isArray( materialValues.diffuse ) ) { + } ); - materialParams.color = new THREE.Color().fromArray( materialValues.diffuse ); + }; - } else if ( typeof( materialValues.diffuse ) === 'string' ) { + GLTFParser.prototype.loadGeometries = function ( primitives ) { - materialParams.map = dependencies.textures[ materialValues.diffuse ]; + return this._withDependencies( [ - } + 'accessors', - delete materialParams.diffuse; + ] ).then( function ( dependencies ) { - if ( typeof( materialValues.reflective ) === 'string' ) { + return _each( primitives, function ( primitive ) { - materialParams.envMap = dependencies.textures[ materialValues.reflective ]; + var geometry = new THREE.BufferGeometry(); - } + var attributes = primitive.attributes; - if ( typeof( materialValues.bump ) === 'string' ) { + for ( var attributeId in attributes ) { - materialParams.bumpMap = dependencies.textures[ materialValues.bump ]; + var attributeEntry = attributes[ attributeId ]; - } + if ( attributeEntry === undefined ) return; - if ( Array.isArray( materialValues.emission ) ) { + var bufferAttribute = dependencies.accessors[ attributeEntry ]; - if ( materialType === THREE.MeshBasicMaterial ) { + switch ( attributeId ) { - materialParams.color = new THREE.Color().fromArray( materialValues.emission ); + case 'POSITION': - } else { + geometry.addAttribute( 'position', bufferAttribute ); + break; - materialParams.emissive = new THREE.Color().fromArray( materialValues.emission ); + case 'NORMAL': - } + geometry.addAttribute( 'normal', bufferAttribute ); + break; - } else if ( typeof( materialValues.emission ) === 'string' ) { + case 'TEXCOORD_0': + case 'TEXCOORD0': + case 'TEXCOORD': - if ( materialType === THREE.MeshBasicMaterial ) { + geometry.addAttribute( 'uv', bufferAttribute ); + break; - materialParams.map = dependencies.textures[ materialValues.emission ]; + case 'TEXCOORD_1': - } else { + geometry.addAttribute( 'uv2', bufferAttribute ); + break; - materialParams.emissiveMap = dependencies.textures[ materialValues.emission ]; + case 'COLOR_0': + case 'COLOR0': + case 'COLOR': - } + geometry.addAttribute( 'color', bufferAttribute ); + break; - } + case 'WEIGHTS_0': + case 'WEIGHT': // WEIGHT semantic deprecated. - if ( Array.isArray( materialValues.specular ) ) { + geometry.addAttribute( 'skinWeight', bufferAttribute ); + break; - materialParams.specular = new THREE.Color().fromArray( materialValues.specular ); + case 'JOINTS_0': + case 'JOINT': // JOINT semantic deprecated. - } else if ( typeof( materialValues.specular ) === 'string' ) { + geometry.addAttribute( 'skinIndex', bufferAttribute ); + break; - materialParams.specularMap = dependencies.textures[ materialValues.specular ]; + } } - if ( materialValues.shininess !== undefined ) { + if ( primitive.indices !== undefined ) { - materialParams.shininess = materialValues.shininess; + geometry.setIndex( dependencies.accessors[ primitive.indices ] ); } - var _material = new materialType( materialParams ); - if ( material.name !== undefined ) _material.name = material.name; - - return _material; + return geometry; } ); @@ -1585,159 +1762,127 @@ THREE.GLTFLoader = ( function () { }; + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes + */ GLTFParser.prototype.loadMeshes = function () { + var scope = this; var json = this.json; return this._withDependencies( [ - "accessors", - "materials" + 'accessors', + 'materials' ] ).then( function ( dependencies ) { - return _each( json.meshes, function ( mesh ) { + return _each( json.meshes, function ( meshDef ) { var group = new THREE.Group(); - if ( mesh.name !== undefined ) group.name = mesh.name; - - if ( mesh.extras ) group.userData = mesh.extras; - var primitives = mesh.primitives || []; + if ( meshDef.name !== undefined ) group.name = meshDef.name; + if ( meshDef.extras ) group.userData = meshDef.extras; - for ( var name in primitives ) { + var primitives = meshDef.primitives || []; - var primitive = primitives[ name ]; + return scope.loadGeometries( primitives ).then( function ( geometries ) { - if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === undefined ) { + for ( var name in primitives ) { - var geometry = new THREE.BufferGeometry(); + var primitive = primitives[ name ]; + var geometry = geometries[ name ]; - var attributes = primitive.attributes; + var material = primitive.material === undefined + ? createDefaultMaterial() + : dependencies.materials[ primitive.material ]; - for ( var attributeId in attributes ) { + if ( material.aoMap + && geometry.attributes.uv2 === undefined + && geometry.attributes.uv !== undefined ) { - var attributeEntry = attributes[ attributeId ]; + console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' ); + geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); - if ( ! attributeEntry ) return; - - var bufferAttribute = dependencies.accessors[ attributeEntry ]; + } - switch ( attributeId ) { + if ( geometry.attributes.color !== undefined ) { - case 'POSITION': - geometry.addAttribute( 'position', bufferAttribute ); - break; + material.vertexColors = THREE.VertexColors; + material.needsUpdate = true; - case 'NORMAL': - geometry.addAttribute( 'normal', bufferAttribute ); - break; + } - case 'TEXCOORD_0': - case 'TEXCOORD0': - case 'TEXCOORD': - geometry.addAttribute( 'uv', bufferAttribute ); - break; + if ( geometry.attributes.normal === undefined ) { - case 'TEXCOORD_1': - geometry.addAttribute( 'uv2', bufferAttribute ); - break; + if ( material.flatShading !== undefined ) { - case 'COLOR_0': - case 'COLOR0': - case 'COLOR': - geometry.addAttribute( 'color', bufferAttribute ); - break; + material.flatShading = true; - case 'WEIGHT': - geometry.addAttribute( 'skinWeight', bufferAttribute ); - break; + } else { - case 'JOINT': - geometry.addAttribute( 'skinIndex', bufferAttribute ); - break; + // TODO: Remove this backwards-compatibility fix after r87 release. + material.shading = THREE.FlatShading; } } - if ( primitive.indices ) { - - geometry.setIndex( dependencies.accessors[ primitive.indices ] ); + var mesh; - } + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === undefined ) { - var material = dependencies.materials !== undefined ? dependencies.materials[ primitive.material ] : createDefaultMaterial(); + mesh = new THREE.Mesh( geometry, material ); - var meshNode = new THREE.Mesh( geometry, material ); - meshNode.castShadow = true; - meshNode.name = ( name === "0" ? group.name : group.name + name ); + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { - if ( primitive.extras ) meshNode.userData = primitive.extras; + mesh = new THREE.Mesh( geometry, material ); + mesh.drawMode = THREE.TriangleStripDrawMode; - group.add( meshNode ); + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { - } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { + mesh = new THREE.Mesh( geometry, material ); + mesh.drawMode = THREE.TriangleFanDrawMode; - var geometry = new THREE.BufferGeometry(); + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { - var attributes = primitive.attributes; + mesh = new THREE.LineSegments( geometry, material ); - for ( var attributeId in attributes ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { - var attributeEntry = attributes[ attributeId ]; + mesh = new THREE.Line( geometry, material ); - if ( ! attributeEntry ) return; + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { - var bufferAttribute = dependencies.accessors[ attributeEntry ]; + mesh = new THREE.LineLoop( geometry, material ); - switch ( attributeId ) { + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { - case 'POSITION': - geometry.addAttribute( 'position', bufferAttribute ); - break; + mesh = new THREE.Points( geometry, material ); - case 'COLOR_0': - case 'COLOR0': - case 'COLOR': - geometry.addAttribute( 'color', bufferAttribute ); - break; + } else { - } + throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ', primitive.mode ); } - var material = dependencies.materials[ primitive.material ]; - - var meshNode; + mesh.name = group.name + '_' + name; - if ( primitive.indices ) { - - geometry.setIndex( dependencies.accessors[ primitive.indices ] ); - - meshNode = new THREE.LineSegments( geometry, material ); - - } else { + if ( primitive.targets !== undefined ) { - meshNode = new THREE.Line( geometry, material ); + addMorphTargets( mesh, meshDef, primitive, dependencies ); } - meshNode.name = ( name === "0" ? group.name : group.name + name ); + if ( primitive.extras ) mesh.userData = primitive.extras; - if ( primitive.extras ) meshNode.userData = primitive.extras; - - group.add( meshNode ); - - } else { - - console.warn( "Only triangular and line primitives are supported" ); + group.add( mesh ); } - } + return group; - return group; + } ); } ); @@ -1745,39 +1890,44 @@ THREE.GLTFLoader = ( function () { }; + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras + */ GLTFParser.prototype.loadCameras = function () { var json = this.json; return _each( json.cameras, function ( camera ) { - if ( camera.type == "perspective" && camera.perspective ) { + var _camera; - var yfov = camera.perspective.yfov; - var aspectRatio = camera.perspective.aspectRatio !== undefined ? camera.perspective.aspectRatio : 1; + var params = camera[ camera.type ]; - // According to COLLADA spec... - // aspectRatio = xfov / yfov - var xfov = yfov * aspectRatio; + if ( !params ) { - var _camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( xfov ), aspectRatio, camera.perspective.znear || 1, camera.perspective.zfar || 2e6 ); - if ( camera.name !== undefined ) _camera.name = camera.name; + console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); + return; - if ( camera.extras ) _camera.userData = camera.extras; + } - return _camera; + if ( camera.type === 'perspective' ) { - } else if ( camera.type == "orthographic" && camera.orthographic ) { + var aspectRatio = params.aspectRatio || 1; + var xfov = params.yfov * aspectRatio; - var _camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, camera.orthographic.znear, camera.orthographic.zfar ); - if ( camera.name !== undefined ) _camera.name = camera.name; + _camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( xfov ), aspectRatio, params.znear || 1, params.zfar || 2e6 ); - if ( camera.extras ) _camera.userData = camera.extras; + } else if ( camera.type === 'orthographic' ) { - return _camera; + _camera = new THREE.OrthographicCamera( params.xmag / -2, params.xmag / 2, params.ymag / 2, params.ymag / -2, params.znear, params.zfar ); } + if ( camera.name !== undefined ) _camera.name = camera.name; + if ( camera.extras ) _camera.userData = camera.extras; + + return _camera; + } ); }; @@ -1788,19 +1938,14 @@ THREE.GLTFLoader = ( function () { return this._withDependencies( [ - "accessors" + 'accessors' ] ).then( function ( dependencies ) { return _each( json.skins, function ( skin ) { - var bindShapeMatrix = new THREE.Matrix4(); - - if ( skin.bindShapeMatrix !== undefined ) bindShapeMatrix.fromArray( skin.bindShapeMatrix ); - var _skin = { - bindShapeMatrix: bindShapeMatrix, - jointNames: skin.jointNames, + joints: skin.joints, inverseBindMatrices: dependencies.accessors[ skin.inverseBindMatrices ] }; @@ -1818,8 +1963,8 @@ THREE.GLTFLoader = ( function () { return this._withDependencies( [ - "accessors", - "nodes" + 'accessors', + 'nodes' ] ).then( function ( dependencies ) { @@ -1835,7 +1980,7 @@ THREE.GLTFLoader = ( function () { if ( sampler ) { var target = channel.target; - var name = target.id; + var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. var input = animation.parameters !== undefined ? animation.parameters[ sampler.input ] : sampler.input; var output = animation.parameters !== undefined ? animation.parameters[ sampler.output ] : sampler.output; @@ -1849,22 +1994,77 @@ THREE.GLTFLoader = ( function () { node.updateMatrix(); node.matrixAutoUpdate = true; - var TypedKeyframeTrack = PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.rotation - ? THREE.QuaternionKeyframeTrack - : THREE.VectorKeyframeTrack; + var TypedKeyframeTrack; + + switch ( PATH_PROPERTIES[ target.path ] ) { + + case PATH_PROPERTIES.weights: + + TypedKeyframeTrack = THREE.NumberKeyframeTrack; + break; + + case PATH_PROPERTIES.rotation: + + TypedKeyframeTrack = THREE.QuaternionKeyframeTrack; + break; + + case PATH_PROPERTIES.position: + case PATH_PROPERTIES.scale: + default: + + TypedKeyframeTrack = THREE.VectorKeyframeTrack; + break; + + } var targetName = node.name ? node.name : node.uuid; + + if ( sampler.interpolation === 'CATMULLROMSPLINE' ) { + + console.warn( 'THREE.GLTFLoader: CATMULLROMSPLINE interpolation is not supported. Using CUBICSPLINE instead.' ); + + } + var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear; + var targetNames = []; + + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { + + // node should be THREE.Group here but + // PATH_PROPERTIES.weights(morphTargetInfluences) should be + // the property of a mesh object under node. + // So finding targets here. + + node.traverse( function ( object ) { + + if ( object.isMesh === true && object.material.morphTargets === true ) { + + targetNames.push( object.name ? object.name : object.uuid ); + + } + + } ); + + } else { + + targetNames.push( targetName ); + + } + // KeyframeTrack.optimize() will modify given 'times' and 'values' // buffers before creating a truncated copy to keep. Because buffers may // be reused by other tracks, make copies here. - tracks.push( new TypedKeyframeTrack( - targetName + '.' + PATH_PROPERTIES[ target.path ], - THREE.AnimationUtils.arraySlice( inputAccessor.array, 0 ), - THREE.AnimationUtils.arraySlice( outputAccessor.array, 0 ), - interpolation - ) ); + for ( var i = 0, il = targetNames.length; i < il; i ++ ) { + + tracks.push( new TypedKeyframeTrack( + targetNames[ i ] + '.' + PATH_PROPERTIES[ target.path ], + THREE.AnimationUtils.arraySlice( inputAccessor.array, 0 ), + THREE.AnimationUtils.arraySlice( outputAccessor.array, 0 ), + interpolation + ) ); + + } } @@ -1872,7 +2072,7 @@ THREE.GLTFLoader = ( function () { } - var name = animation.name !== undefined ? animation.name : "animation_" + animationId; + var name = animation.name !== undefined ? animation.name : 'animation_' + animationId; return new THREE.AnimationClip( name, undefined, tracks ); @@ -1888,22 +2088,30 @@ THREE.GLTFLoader = ( function () { var extensions = this.extensions; var scope = this; - return _each( json.nodes, function ( node ) { + var nodes = json.nodes || []; + var skins = json.skins || []; - var matrix = new THREE.Matrix4(); + // Nothing in the node definition indicates whether it is a Bone or an + // Object3D. Use the skins' joint references to mark bones. + skins.forEach( function ( skin ) { - var _node; + skin.joints.forEach( function ( id ) { - if ( node.jointName ) { + nodes[ id ].isBone = true; - _node = new THREE.Bone(); - _node.name = node.name !== undefined ? node.name : node.jointName; - _node.jointName = node.jointName; + } ); - } else { + } ); + + return _each( json.nodes, function ( node ) { + + var matrix = new THREE.Matrix4(); + + var _node = node.isBone === true ? new THREE.Bone() : new THREE.Object3D(); - _node = new THREE.Object3D(); - if ( node.name !== undefined ) _node.name = node.name; + if ( node.name !== undefined ) { + + _node.name = THREE.PropertyBinding.sanitizeNodeName( node.name ); } @@ -1942,9 +2150,9 @@ THREE.GLTFLoader = ( function () { return scope._withDependencies( [ - "meshes", - "skins", - "cameras" + 'meshes', + 'skins', + 'cameras' ] ).then( function ( dependencies ) { @@ -1952,42 +2160,51 @@ THREE.GLTFLoader = ( function () { var node = json.nodes[ nodeId ]; - if ( node.meshes !== undefined ) { + var meshes; + + if ( node.mesh !== undefined) { + + meshes = [ node.mesh ]; + + } else if ( node.meshes !== undefined ) { + + console.warn( 'THREE.GLTFLoader: Legacy glTF file detected. Nodes may have no more than one mesh.' ); + + meshes = node.meshes; + + } + + if ( meshes !== undefined ) { - for ( var meshId in node.meshes ) { + for ( var meshId in meshes ) { - var mesh = node.meshes[ meshId ]; + var mesh = meshes[ meshId ]; var group = dependencies.meshes[ mesh ]; if ( group === undefined ) { - console.warn( 'GLTFLoader: Couldn\'t find node "' + mesh + '".' ); + console.warn( 'THREE.GLTFLoader: Could not find node "' + mesh + '".' ); continue; } + // do not clone children as they will be replaced anyway + var clonedgroup = group.clone( false ); + for ( var childrenId in group.children ) { var child = group.children[ childrenId ]; + var originalChild = child; // clone Mesh to add to _node var originalMaterial = child.material; var originalGeometry = child.geometry; + var originalInfluences = child.morphTargetInfluences; var originalUserData = child.userData; var originalName = child.name; - var material; - - if ( originalMaterial.isDeferredShaderMaterial ) { - - originalMaterial = material = originalMaterial.create(); - - } else { - - material = originalMaterial; - - } + var material = originalMaterial; switch ( child.type ) { @@ -2003,18 +2220,24 @@ THREE.GLTFLoader = ( function () { child = new THREE.Line( originalGeometry, material ); break; + case 'Points': + child = new THREE.Points( originalGeometry, material ); + break; + default: child = new THREE.Mesh( originalGeometry, material ); + child.drawMode = originalChild.drawMode; } child.castShadow = true; + child.morphTargetInfluences = originalInfluences; child.userData = originalUserData; child.name = originalName; var skinEntry; - if ( node.skin ) { + if ( node.skin !== undefined ) { skinEntry = dependencies.skins[ node.skin ]; @@ -2023,24 +2246,8 @@ THREE.GLTFLoader = ( function () { // Replace Mesh with SkinnedMesh in library if ( skinEntry ) { - var getJointNode = function ( jointId ) { - - var keys = Object.keys( __nodes ); - - for ( var i = 0, il = keys.length; i < il; i ++ ) { - - var n = __nodes[ keys[ i ] ]; - - if ( n.jointName === jointId ) return n; - - } - - return null; - - }; - var geometry = originalGeometry; - var material = originalMaterial; + material = originalMaterial; material.skinning = true; child = new THREE.SkinnedMesh( geometry, material ); @@ -2051,10 +2258,10 @@ THREE.GLTFLoader = ( function () { var bones = []; var boneInverses = []; - for ( var i = 0, l = skinEntry.jointNames.length; i < l; i ++ ) { + for ( var i = 0, l = skinEntry.joints.length; i < l; i ++ ) { - var jointId = skinEntry.jointNames[ i ]; - var jointNode = getJointNode( jointId ); + var jointId = skinEntry.joints[ i ]; + var jointNode = __nodes[ jointId ]; if ( jointNode ) { @@ -2066,45 +2273,22 @@ THREE.GLTFLoader = ( function () { } else { - console.warn( "WARNING: joint: '" + jointId + "' could not be found" ); + console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', jointId ); } } - child.bind( new THREE.Skeleton( bones, boneInverses ), skinEntry.bindShapeMatrix ); - - var buildBoneGraph = function ( parentJson, parentObject, property ) { - - var children = parentJson[ property ]; - - if ( children === undefined ) return; - - for ( var i = 0, il = children.length; i < il; i ++ ) { - - var nodeId = children[ i ]; - var bone = __nodes[ nodeId ]; - var boneJson = json.nodes[ nodeId ]; - - if ( bone !== undefined && bone.isBone === true && boneJson !== undefined ) { - - parentObject.add( bone ); - buildBoneGraph( boneJson, bone, 'children' ); - - } - - } - - }; - - buildBoneGraph( node, child, 'skeletons' ); + child.bind( new THREE.Skeleton( bones, boneInverses ), child.matrixWorld ); } - _node.add( child ); + clonedgroup.add( child ); } + _node.add( clonedgroup ); + } } @@ -2118,13 +2302,11 @@ THREE.GLTFLoader = ( function () { } if ( node.extensions - && node.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ] - && node.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ].light ) { + && node.extensions[ EXTENSIONS.KHR_LIGHTS ] + && node.extensions[ EXTENSIONS.KHR_LIGHTS ].light !== undefined ) { - var extensionLights = extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ].lights; - var light = extensionLights[ node.extensions[ EXTENSIONS.KHR_MATERIALS_COMMON ].light ]; - - _node.add( light ); + var lights = extensions[ EXTENSIONS.KHR_LIGHTS ].lights; + _node.add( lights[ node.extensions[ EXTENSIONS.KHR_LIGHTS ].light ] ); } @@ -2141,6 +2323,7 @@ THREE.GLTFLoader = ( function () { GLTFParser.prototype.loadScenes = function () { var json = this.json; + var extensions = this.extensions; // scene node hierachy builder @@ -2168,7 +2351,7 @@ THREE.GLTFLoader = ( function () { return this._withDependencies( [ - "nodes" + 'nodes' ] ).then( function ( dependencies ) { @@ -2190,18 +2373,25 @@ THREE.GLTFLoader = ( function () { _scene.traverse( function ( child ) { - // Register raw material meshes with GLTFLoader.Shaders - if ( child.material && child.material.isRawShaderMaterial ) { + // for Specular-Glossiness. + if ( child.material && child.material.isGLTFSpecularGlossinessMaterial ) { - child.gltfShader = new GLTFShader( child, dependencies.nodes ); - child.onBeforeRender = function(renderer, scene, camera){ - this.gltfShader.update(scene, camera); - }; + child.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; } } ); + // Ambient lighting, if present, is always attached to the scene root. + if ( scene.extensions + && scene.extensions[ EXTENSIONS.KHR_LIGHTS ] + && scene.extensions[ EXTENSIONS.KHR_LIGHTS ].light !== undefined ) { + + var lights = extensions[ EXTENSIONS.KHR_LIGHTS ].lights; + _scene.add( lights[ scene.extensions[ EXTENSIONS.KHR_LIGHTS ].light ] ); + + } + return _scene; } ); diff --git a/examples/webgl_exporter_gltf2.html b/examples/webgl_exporter_gltf.html similarity index 99% rename from examples/webgl_exporter_gltf2.html rename to examples/webgl_exporter_gltf.html index e69c65c9fbdd6bf159e90bf41a54d6f6223962b8..3b600796547ab8becdca740fe7888cbe843b282c 100644 --- a/examples/webgl_exporter_gltf2.html +++ b/examples/webgl_exporter_gltf.html @@ -22,7 +22,7 @@
- GLTF2 Exporter
+ GLTF Exporter
diff --git a/examples/webgl_loader_gltf2.html b/examples/webgl_loader_gltf.html similarity index 99% rename from examples/webgl_loader_gltf2.html rename to examples/webgl_loader_gltf.html index 3a15be1d8b4ada4ddee55ef57ceb44947ae1006d..7e85e7528993b2bd957f63b7b0b357d39141c0ff 100644 --- a/examples/webgl_loader_gltf2.html +++ b/examples/webgl_loader_gltf.html @@ -96,7 +96,7 @@
- +