three.gltfloader = ( function () { function gltfloader( manager ) { three.loader.call( this, manager ); this.dracoloader = null; this.ddsloader = null; this.ktx2loader = null; this.meshoptdecoder = null; this.plugincallbacks = []; this.register( function ( parser ) { return new gltfmaterialsclearcoatextension( parser ); } ); this.register( function ( parser ) { return new gltftexturebasisuextension( parser ); } ); this.register( function ( parser ) { return new gltftexturewebpextension( parser ); } ); this.register( function ( parser ) { return new gltfmaterialstransmissionextension( parser ); } ); this.register( function ( parser ) { return new gltflightsextension( parser ); } ); this.register( function ( parser ) { return new gltfmeshoptcompression( parser ); } ); } gltfloader.prototype = object.assign( object.create( three.loader.prototype ), { constructor: gltfloader, load: function ( url, onload, onprogress, onerror ) { var scope = this; var resourcepath; if ( this.resourcepath !== '' ) { resourcepath = this.resourcepath; } else if ( this.path !== '' ) { resourcepath = this.path; } else { resourcepath = three.loaderutils.extracturlbase( url ); } // tells the loadingmanager to track an extra item, which resolves after // the model is fully loaded. this means the count of items loaded will // be incorrect, but ensures manager.onload() does not fire early. this.manager.itemstart( url ); var _onerror = function ( e ) { if ( onerror ) { onerror( e ); } else { console.error( e ); } scope.manager.itemerror( url ); scope.manager.itemend( url ); }; var loader = new three.fileloader( this.manager ); loader.setpath( this.path ); loader.setresponsetype( 'arraybuffer' ); loader.setrequestheader( this.requestheader ); loader.setwithcredentials( this.withcredentials ); loader.load( url, function ( data ) { try { scope.parse( data, resourcepath, function ( gltf ) { onload( gltf ); scope.manager.itemend( url ); }, _onerror ); } catch ( e ) { _onerror( e ); } }, onprogress, _onerror ); }, setdracoloader: function ( dracoloader ) { this.dracoloader = dracoloader; return this; }, setddsloader: function ( ddsloader ) { this.ddsloader = ddsloader; return this; }, setktx2loader: function ( ktx2loader ) { this.ktx2loader = ktx2loader; return this; }, setmeshoptdecoder: function ( meshoptdecoder ) { this.meshoptdecoder = meshoptdecoder; return this; }, register: function ( callback ) { if ( this.plugincallbacks.indexof( callback ) === - 1 ) { this.plugincallbacks.push( callback ); } return this; }, unregister: function ( callback ) { if ( this.plugincallbacks.indexof( callback ) !== - 1 ) { this.plugincallbacks.splice( this.plugincallbacks.indexof( callback ), 1 ); } return this; }, parse: function ( data, path, onload, onerror ) { var content; var extensions = {}; var plugins = {}; if ( typeof data === 'string' ) { content = data; } else { var magic = three.loaderutils.decodetext( new uint8array( data, 0, 4 ) ); if ( magic === binary_extension_header_magic ) { try { extensions[ extensions.khr_binary_gltf ] = new gltfbinaryextension( data ); } catch ( error ) { if ( onerror ) onerror( error ); return; } content = extensions[ extensions.khr_binary_gltf ].content; } else { content = three.loaderutils.decodetext( new uint8array( data ) ); } } var json = json.parse( content ); if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { if ( onerror ) onerror( new error( 'three.gltfloader: unsupported asset. gltf versions >=2.0 are supported.' ) ); return; } var parser = new gltfparser( json, { path: path || this.resourcepath || '', crossorigin: this.crossorigin, manager: this.manager, ktx2loader: this.ktx2loader, meshoptdecoder: this.meshoptdecoder } ); parser.fileloader.setrequestheader( this.requestheader ); for ( var i = 0; i < this.plugincallbacks.length; i ++ ) { var plugin = this.plugincallbacks[ i ]( parser ); plugins[ plugin.name ] = plugin; // workaround to avoid determining as unknown extension // in addunknownextensionstouserdata(). // remove this workaround if we move all the existing // extension handlers to plugin system extensions[ plugin.name ] = true; } if ( json.extensionsused ) { for ( var i = 0; i < json.extensionsused.length; ++ i ) { var extensionname = json.extensionsused[ i ]; var extensionsrequired = json.extensionsrequired || []; switch ( extensionname ) { case extensions.khr_materials_unlit: extensions[ extensionname ] = new gltfmaterialsunlitextension(); break; case extensions.khr_materials_pbr_specular_glossiness: extensions[ extensionname ] = new gltfmaterialspbrspecularglossinessextension(); break; case extensions.khr_draco_mesh_compression: extensions[ extensionname ] = new gltfdracomeshcompressionextension( json, this.dracoloader ); break; case extensions.msft_texture_dds: extensions[ extensionname ] = new gltftextureddsextension( this.ddsloader ); break; case extensions.khr_texture_transform: extensions[ extensionname ] = new gltftexturetransformextension(); break; case extensions.khr_mesh_quantization: extensions[ extensionname ] = new gltfmeshquantizationextension(); break; default: if ( extensionsrequired.indexof( extensionname ) >= 0 && plugins[ extensionname ] === undefined ) { console.warn( 'three.gltfloader: unknown extension "' + extensionname + '".' ); } } } } parser.setextensions( extensions ); parser.setplugins( plugins ); parser.parse( onload, 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 = {}; } }; } /*********************************/ /********** extensions ***********/ /*********************************/ var extensions = { khr_binary_gltf: 'khr_binary_gltf', khr_draco_mesh_compression: 'khr_draco_mesh_compression', khr_lights_punctual: 'khr_lights_punctual', khr_materials_clearcoat: 'khr_materials_clearcoat', khr_materials_pbr_specular_glossiness: 'khr_materials_pbrspecularglossiness', khr_materials_transmission: 'khr_materials_transmission', khr_materials_unlit: 'khr_materials_unlit', khr_texture_basisu: 'khr_texture_basisu', khr_texture_transform: 'khr_texture_transform', khr_mesh_quantization: 'khr_mesh_quantization', ext_texture_webp: 'ext_texture_webp', ext_meshopt_compression: 'ext_meshopt_compression', msft_texture_dds: 'msft_texture_dds' }; /** * dds texture extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/vendor/msft_texture_dds * */ function gltftextureddsextension( ddsloader ) { if ( ! ddsloader ) { throw new error( 'three.gltfloader: attempting to load .dds texture without importing three.ddsloader' ); } this.name = extensions.msft_texture_dds; this.ddsloader = ddsloader; } /** * punctual lights extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_lights_punctual */ function gltflightsextension( parser ) { this.parser = parser; this.name = extensions.khr_lights_punctual; // object3d instance caches this.cache = { refs: {}, uses: {} }; } gltflightsextension.prototype._markdefs = function () { var parser = this.parser; var nodedefs = this.parser.json.nodes || []; for ( var nodeindex = 0, nodelength = nodedefs.length; nodeindex < nodelength; nodeindex ++ ) { var nodedef = nodedefs[ nodeindex ]; if ( nodedef.extensions && nodedef.extensions[ this.name ] && nodedef.extensions[ this.name ].light !== undefined ) { parser._addnoderef( this.cache, nodedef.extensions[ this.name ].light ); } } }; gltflightsextension.prototype._loadlight = function ( lightindex ) { var parser = this.parser; var cachekey = 'light:' + lightindex; var dependency = parser.cache.get( cachekey ); if ( dependency ) return dependency; var json = parser.json; var extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; var lightdefs = extensions.lights || []; var lightdef = lightdefs[ lightindex ]; var lightnode; var color = new three.color( 0xffffff ); if ( lightdef.color !== undefined ) color.fromarray( lightdef.color ); var range = lightdef.range !== undefined ? lightdef.range : 0; switch ( lightdef.type ) { case 'directional': lightnode = new three.directionallight( color ); lightnode.target.position.set( 0, 0, - 1 ); lightnode.add( lightnode.target ); break; case 'point': lightnode = new three.pointlight( color ); lightnode.distance = range; break; case 'spot': lightnode = new three.spotlight( color ); lightnode.distance = range; // handle spotlight properties. lightdef.spot = lightdef.spot || {}; lightdef.spot.innerconeangle = lightdef.spot.innerconeangle !== undefined ? lightdef.spot.innerconeangle : 0; lightdef.spot.outerconeangle = lightdef.spot.outerconeangle !== undefined ? lightdef.spot.outerconeangle : math.pi / 4.0; lightnode.angle = lightdef.spot.outerconeangle; lightnode.penumbra = 1.0 - lightdef.spot.innerconeangle / lightdef.spot.outerconeangle; lightnode.target.position.set( 0, 0, - 1 ); lightnode.add( lightnode.target ); break; default: throw new error( 'three.gltfloader: unexpected light type, "' + lightdef.type + '".' ); } // some lights (e.g. spot) default to a position other than the origin. reset the position // here, because node-level parsing will only override position if explicitly specified. lightnode.position.set( 0, 0, 0 ); lightnode.decay = 2; if ( lightdef.intensity !== undefined ) lightnode.intensity = lightdef.intensity; lightnode.name = parser.createuniquename( lightdef.name || ( 'light_' + lightindex ) ); dependency = promise.resolve( lightnode ); parser.cache.add( cachekey, dependency ); return dependency; }; gltflightsextension.prototype.createnodeattachment = function ( nodeindex ) { var self = this; var parser = this.parser; var json = parser.json; var nodedef = json.nodes[ nodeindex ]; var lightdef = ( nodedef.extensions && nodedef.extensions[ this.name ] ) || {}; var lightindex = lightdef.light; if ( lightindex === undefined ) return null; return this._loadlight( lightindex ).then( function ( light ) { return parser._getnoderef( self.cache, lightindex, light ); } ); }; /** * unlit materials extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_materials_unlit */ function gltfmaterialsunlitextension() { this.name = extensions.khr_materials_unlit; } gltfmaterialsunlitextension.prototype.getmaterialtype = function () { return three.meshbasicmaterial; }; gltfmaterialsunlitextension.prototype.extendparams = function ( materialparams, materialdef, parser ) { var pending = []; materialparams.color = new three.color( 1.0, 1.0, 1.0 ); materialparams.opacity = 1.0; var metallicroughness = materialdef.pbrmetallicroughness; if ( metallicroughness ) { 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 ) ); } } return promise.all( pending ); }; /** * clearcoat materials extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_materials_clearcoat */ function gltfmaterialsclearcoatextension( parser ) { this.parser = parser; this.name = extensions.khr_materials_clearcoat; } gltfmaterialsclearcoatextension.prototype.getmaterialtype = function ( materialindex ) { var parser = this.parser; var materialdef = parser.json.materials[ materialindex ]; if ( ! materialdef.extensions || ! materialdef.extensions[ this.name ] ) return null; return three.meshphysicalmaterial; }; gltfmaterialsclearcoatextension.prototype.extendmaterialparams = function ( materialindex, materialparams ) { var parser = this.parser; var materialdef = parser.json.materials[ materialindex ]; if ( ! materialdef.extensions || ! materialdef.extensions[ this.name ] ) { return promise.resolve(); } var pending = []; var extension = materialdef.extensions[ this.name ]; if ( extension.clearcoatfactor !== undefined ) { materialparams.clearcoat = extension.clearcoatfactor; } if ( extension.clearcoattexture !== undefined ) { pending.push( parser.assigntexture( materialparams, 'clearcoatmap', extension.clearcoattexture ) ); } if ( extension.clearcoatroughnessfactor !== undefined ) { materialparams.clearcoatroughness = extension.clearcoatroughnessfactor; } if ( extension.clearcoatroughnesstexture !== undefined ) { pending.push( parser.assigntexture( materialparams, 'clearcoatroughnessmap', extension.clearcoatroughnesstexture ) ); } if ( extension.clearcoatnormaltexture !== undefined ) { pending.push( parser.assigntexture( materialparams, 'clearcoatnormalmap', extension.clearcoatnormaltexture ) ); if ( extension.clearcoatnormaltexture.scale !== undefined ) { var scale = extension.clearcoatnormaltexture.scale; materialparams.clearcoatnormalscale = new three.vector2( scale, scale ); } } return promise.all( pending ); }; /** * transmission materials extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_materials_transmission * draft: https://github.com/khronosgroup/gltf/pull/1698 */ function gltfmaterialstransmissionextension( parser ) { this.parser = parser; this.name = extensions.khr_materials_transmission; } gltfmaterialstransmissionextension.prototype.getmaterialtype = function ( materialindex ) { var parser = this.parser; var materialdef = parser.json.materials[ materialindex ]; if ( ! materialdef.extensions || ! materialdef.extensions[ this.name ] ) return null; return three.meshphysicalmaterial; }; gltfmaterialstransmissionextension.prototype.extendmaterialparams = function ( materialindex, materialparams ) { var parser = this.parser; var materialdef = parser.json.materials[ materialindex ]; if ( ! materialdef.extensions || ! materialdef.extensions[ this.name ] ) { return promise.resolve(); } var pending = []; var extension = materialdef.extensions[ this.name ]; if ( extension.transmissionfactor !== undefined ) { materialparams.transmission = extension.transmissionfactor; } if ( extension.transmissiontexture !== undefined ) { pending.push( parser.assigntexture( materialparams, 'transmissionmap', extension.transmissiontexture ) ); } return promise.all( pending ); }; /** * basisu texture extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_texture_basisu */ function gltftexturebasisuextension( parser ) { this.parser = parser; this.name = extensions.khr_texture_basisu; } gltftexturebasisuextension.prototype.loadtexture = function ( textureindex ) { var parser = this.parser; var json = parser.json; var texturedef = json.textures[ textureindex ]; if ( ! texturedef.extensions || ! texturedef.extensions[ this.name ] ) { return null; } var extension = texturedef.extensions[ this.name ]; var source = json.images[ extension.source ]; var loader = parser.options.ktx2loader; if ( ! loader ) { if ( json.extensionsrequired && json.extensionsrequired.indexof( this.name ) >= 0 ) { throw new error( 'three.gltfloader: setktx2loader must be called before loading ktx2 textures' ); } else { // assumes that the extension is optional and that a fallback texture is present return null; } } return parser.loadtextureimage( textureindex, source, loader ); }; /** * webp texture extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/vendor/ext_texture_webp */ function gltftexturewebpextension( parser ) { this.parser = parser; this.name = extensions.ext_texture_webp; this.issupported = null; } gltftexturewebpextension.prototype.loadtexture = function ( textureindex ) { var name = this.name; var parser = this.parser; var json = parser.json; var texturedef = json.textures[ textureindex ]; if ( ! texturedef.extensions || ! texturedef.extensions[ name ] ) { return null; } var extension = texturedef.extensions[ name ]; var source = json.images[ extension.source ]; var loader = source.uri ? parser.options.manager.gethandler( source.uri ) : parser.textureloader; return this.detectsupport().then( function ( issupported ) { if ( issupported ) return parser.loadtextureimage( textureindex, source, loader ); if ( json.extensionsrequired && json.extensionsrequired.indexof( name ) >= 0 ) { throw new error( 'three.gltfloader: webp required by asset but unsupported.' ); } // fall back to png or jpeg. return parser.loadtexture( textureindex ); } ); }; gltftexturewebpextension.prototype.detectsupport = function () { if ( ! this.issupported ) { this.issupported = new promise( function ( resolve ) { var image = new image(); // lossy test image. support for lossy images doesn't guarantee support for all // webp images, unfortunately. image.src = 'data:image/webp;base64,uklgriiaaabxrujqvla4ibyaaaawaqcdasobaaeadsd+jaqaa3aaaaaa'; image.onload = image.onerror = function () { resolve( image.height === 1 ); }; } ); } return this.issupported; }; /** * meshopt bufferview compression extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/vendor/ext_meshopt_compression */ function gltfmeshoptcompression( parser ) { this.name = extensions.ext_meshopt_compression; this.parser = parser; } gltfmeshoptcompression.prototype.loadbufferview = function ( index ) { var json = this.parser.json; var bufferview = json.bufferviews[ index ]; if ( bufferview.extensions && bufferview.extensions[ this.name ] ) { var extensiondef = bufferview.extensions[ this.name ]; var buffer = this.parser.getdependency( 'buffer', extensiondef.buffer ); var decoder = this.parser.options.meshoptdecoder; if ( ! decoder || ! decoder.supported ) { if ( json.extensionsrequired && json.extensionsrequired.indexof( this.name ) >= 0 ) { throw new error( 'three.gltfloader: setmeshoptdecoder must be called before loading compressed files' ); } else { // assumes that the extension is optional and that fallback buffer data is present return null; } } return promise.all( [ buffer, decoder.ready ] ).then( function ( res ) { var byteoffset = extensiondef.byteoffset || 0; var bytelength = extensiondef.bytelength || 0; var count = extensiondef.count; var stride = extensiondef.bytestride; var result = new arraybuffer( count * stride ); var source = new uint8array( res[ 0 ], byteoffset, bytelength ); decoder.decodegltfbuffer( new uint8array( result ), count, stride, source, extensiondef.mode, extensiondef.filter ); return result; } ); } else { return null; } }; /* binary extension */ 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: three.loaderutils.decodetext( new uint8array( data.slice( 0, 4 ) ) ), version: headerview.getuint32( 4, true ), length: headerview.getuint32( 8, true ) }; if ( this.header.magic !== binary_extension_header_magic ) { throw new error( 'three.gltfloader: unsupported gltf-binary header.' ); } else if ( this.header.version < 2.0 ) { throw new error( 'three.gltfloader: legacy binary file detected.' ); } 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 = three.loaderutils.decodetext( 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.gltfloader: json content not found.' ); } } /** * draco mesh compression extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_draco_mesh_compression */ function gltfdracomeshcompressionextension( json, dracoloader ) { if ( ! dracoloader ) { throw new error( 'three.gltfloader: no dracoloader instance provided.' ); } this.name = extensions.khr_draco_mesh_compression; this.json = json; this.dracoloader = dracoloader; this.dracoloader.preload(); } gltfdracomeshcompressionextension.prototype.decodeprimitive = function ( primitive, parser ) { var json = this.json; var dracoloader = this.dracoloader; var bufferviewindex = primitive.extensions[ this.name ].bufferview; var gltfattributemap = primitive.extensions[ this.name ].attributes; var threeattributemap = {}; var attributenormalizedmap = {}; var attributetypemap = {}; for ( var attributename in gltfattributemap ) { var threeattributename = attributes[ attributename ] || attributename.tolowercase(); threeattributemap[ threeattributename ] = gltfattributemap[ attributename ]; } for ( attributename in primitive.attributes ) { var threeattributename = attributes[ attributename ] || attributename.tolowercase(); if ( gltfattributemap[ attributename ] !== undefined ) { var accessordef = json.accessors[ primitive.attributes[ attributename ] ]; var componenttype = webgl_component_types[ accessordef.componenttype ]; attributetypemap[ threeattributename ] = componenttype; attributenormalizedmap[ threeattributename ] = accessordef.normalized === true; } } return parser.getdependency( 'bufferview', bufferviewindex ).then( function ( bufferview ) { return new promise( function ( resolve ) { dracoloader.decodedracofile( bufferview, function ( geometry ) { for ( var attributename in geometry.attributes ) { var attribute = geometry.attributes[ attributename ]; var normalized = attributenormalizedmap[ attributename ]; if ( normalized !== undefined ) attribute.normalized = normalized; } resolve( geometry ); }, threeattributemap, attributetypemap ); } ); } ); }; /** * texture transform extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_texture_transform */ function gltftexturetransformextension() { this.name = extensions.khr_texture_transform; } gltftexturetransformextension.prototype.extendtexture = function ( texture, transform ) { texture = texture.clone(); if ( transform.offset !== undefined ) { texture.offset.fromarray( transform.offset ); } if ( transform.rotation !== undefined ) { texture.rotation = transform.rotation; } if ( transform.scale !== undefined ) { texture.repeat.fromarray( transform.scale ); } if ( transform.texcoord !== undefined ) { console.warn( 'three.gltfloader: custom uv sets in "' + this.name + '" extension not yet supported.' ); } texture.needsupdate = true; return texture; }; /** * specular-glossiness extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_materials_pbrspecularglossiness */ /** * a sub class of three.standardmaterial with some of the functionality * changed via the `onbeforecompile` callback * @pailhead */ function gltfmeshstandardsgmaterial( params ) { three.meshstandardmaterial.call( this ); this.isgltfspecularglossinessmaterial = true; //various chunks that need replacing var specularmapparsfragmentchunk = [ '#ifdef use_specularmap', ' uniform sampler2d specularmap;', '#endif' ].join( '\n' ); var glossinessmapparsfragmentchunk = [ '#ifdef use_glossinessmap', ' uniform sampler2d glossinessmap;', '#endif' ].join( '\n' ); var specularmapfragmentchunk = [ 'vec3 specularfactor = specular;', '#ifdef use_specularmap', ' vec4 texelspecular = texture2d( specularmap, vuv );', ' texelspecular = srgbtolinear( texelspecular );', ' // reads channel rgb, compatible with a gltf specular-glossiness (rgba) texture', ' specularfactor *= texelspecular.rgb;', '#endif' ].join( '\n' ); var glossinessmapfragmentchunk = [ 'float glossinessfactor = glossiness;', '#ifdef use_glossinessmap', ' vec4 texelglossiness = texture2d( glossinessmap, vuv );', ' // reads channel a, compatible with a gltf specular-glossiness (rgba) texture', ' glossinessfactor *= texelglossiness.a;', '#endif' ].join( '\n' ); var lightphysicalfragmentchunk = [ 'physicalmaterial material;', 'material.diffusecolor = diffusecolor.rgb * ( 1. - max( specularfactor.r, max( specularfactor.g, specularfactor.b ) ) );', 'vec3 dxy = max( abs( dfdx( geometrynormal ) ), abs( dfdy( geometrynormal ) ) );', 'float geometryroughness = max( max( dxy.x, dxy.y ), dxy.z );', 'material.specularroughness = max( 1.0 - glossinessfactor, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.', 'material.specularroughness += geometryroughness;', 'material.specularroughness = min( material.specularroughness, 1.0 );', 'material.specularcolor = specularfactor;', ].join( '\n' ); var uniforms = { specular: { value: new three.color().sethex( 0xffffff ) }, glossiness: { value: 1 }, specularmap: { value: null }, glossinessmap: { value: null } }; this._extrauniforms = uniforms; this.onbeforecompile = function ( shader ) { for ( var uniformname in uniforms ) { shader.uniforms[ uniformname ] = uniforms[ uniformname ]; } shader.fragmentshader = shader.fragmentshader .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) .replace( 'uniform float metalness;', 'uniform float glossiness;' ) .replace( '#include ', specularmapparsfragmentchunk ) .replace( '#include ', glossinessmapparsfragmentchunk ) .replace( '#include ', specularmapfragmentchunk ) .replace( '#include ', glossinessmapfragmentchunk ) .replace( '#include ', lightphysicalfragmentchunk ); }; object.defineproperties( this, { specular: { get: function () { return uniforms.specular.value; }, set: function ( v ) { uniforms.specular.value = v; } }, specularmap: { get: function () { return uniforms.specularmap.value; }, set: function ( v ) { uniforms.specularmap.value = v; if ( v ) { this.defines.use_specularmap = ''; // use_uv is set by the renderer for specular maps } else { delete this.defines.use_specularmap; } } }, glossiness: { get: function () { return uniforms.glossiness.value; }, set: function ( v ) { uniforms.glossiness.value = v; } }, glossinessmap: { get: function () { return uniforms.glossinessmap.value; }, set: function ( v ) { uniforms.glossinessmap.value = v; if ( v ) { this.defines.use_glossinessmap = ''; this.defines.use_uv = ''; } else { delete this.defines.use_glossinessmap; delete this.defines.use_uv; } } } } ); delete this.metalness; delete this.roughness; delete this.metalnessmap; delete this.roughnessmap; this.setvalues( params ); } gltfmeshstandardsgmaterial.prototype = object.create( three.meshstandardmaterial.prototype ); gltfmeshstandardsgmaterial.prototype.constructor = gltfmeshstandardsgmaterial; gltfmeshstandardsgmaterial.prototype.copy = function ( source ) { three.meshstandardmaterial.prototype.copy.call( this, source ); this.specularmap = source.specularmap; this.specular.copy( source.specular ); this.glossinessmap = source.glossinessmap; this.glossiness = source.glossiness; delete this.metalness; delete this.roughness; delete this.metalnessmap; delete this.roughnessmap; return this; }; function gltfmaterialspbrspecularglossinessextension() { return { name: extensions.khr_materials_pbr_specular_glossiness, specularglossinessparams: [ 'color', 'map', 'lightmap', 'lightmapintensity', 'aomap', 'aomapintensity', 'emissive', 'emissiveintensity', 'emissivemap', 'bumpmap', 'bumpscale', 'normalmap', 'normalmaptype', 'displacementmap', 'displacementscale', 'displacementbias', 'specularmap', 'specular', 'glossinessmap', 'glossiness', 'alphamap', 'envmap', 'envmapintensity', 'refractionratio', ], getmaterialtype: function () { return gltfmeshstandardsgmaterial; }, extendparams: function ( materialparams, materialdef, parser ) { var pbrspecularglossiness = materialdef.extensions[ this.name ]; materialparams.color = new three.color( 1.0, 1.0, 1.0 ); materialparams.opacity = 1.0; var pending = []; if ( array.isarray( pbrspecularglossiness.diffusefactor ) ) { var array = pbrspecularglossiness.diffusefactor; materialparams.color.fromarray( array ); materialparams.opacity = array[ 3 ]; } if ( pbrspecularglossiness.diffusetexture !== undefined ) { pending.push( parser.assigntexture( materialparams, 'map', pbrspecularglossiness.diffusetexture ) ); } materialparams.emissive = new three.color( 0.0, 0.0, 0.0 ); materialparams.glossiness = pbrspecularglossiness.glossinessfactor !== undefined ? pbrspecularglossiness.glossinessfactor : 1.0; materialparams.specular = new three.color( 1.0, 1.0, 1.0 ); if ( array.isarray( pbrspecularglossiness.specularfactor ) ) { materialparams.specular.fromarray( pbrspecularglossiness.specularfactor ); } if ( pbrspecularglossiness.specularglossinesstexture !== undefined ) { var specglossmapdef = pbrspecularglossiness.specularglossinesstexture; pending.push( parser.assigntexture( materialparams, 'glossinessmap', specglossmapdef ) ); pending.push( parser.assigntexture( materialparams, 'specularmap', specglossmapdef ) ); } return promise.all( pending ); }, creatematerial: function ( materialparams ) { var material = new gltfmeshstandardsgmaterial( materialparams ); material.fog = true; material.color = materialparams.color; material.map = materialparams.map === undefined ? null : materialparams.map; material.lightmap = null; material.lightmapintensity = 1.0; material.aomap = materialparams.aomap === undefined ? null : materialparams.aomap; material.aomapintensity = 1.0; material.emissive = materialparams.emissive; material.emissiveintensity = 1.0; material.emissivemap = materialparams.emissivemap === undefined ? null : materialparams.emissivemap; material.bumpmap = materialparams.bumpmap === undefined ? null : materialparams.bumpmap; material.bumpscale = 1; material.normalmap = materialparams.normalmap === undefined ? null : materialparams.normalmap; material.normalmaptype = three.tangentspacenormalmap; if ( materialparams.normalscale ) material.normalscale = materialparams.normalscale; material.displacementmap = null; material.displacementscale = 1; material.displacementbias = 0; material.specularmap = materialparams.specularmap === undefined ? null : materialparams.specularmap; material.specular = materialparams.specular; material.glossinessmap = materialparams.glossinessmap === undefined ? null : materialparams.glossinessmap; material.glossiness = materialparams.glossiness; material.alphamap = null; material.envmap = materialparams.envmap === undefined ? null : materialparams.envmap; material.envmapintensity = 1.0; material.refractionratio = 0.98; return material; }, }; } /** * mesh quantization extension * * specification: https://github.com/khronosgroup/gltf/tree/master/extensions/2.0/khronos/khr_mesh_quantization */ function gltfmeshquantizationextension() { this.name = extensions.khr_mesh_quantization; } /*********************************/ /********** interpolation ********/ /*********************************/ // spline interpolation // specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#appendix-c-spline-interpolation function gltfcubicsplineinterpolant( parameterpositions, samplevalues, samplesize, resultbuffer ) { three.interpolant.call( this, parameterpositions, samplevalues, samplesize, resultbuffer ); } gltfcubicsplineinterpolant.prototype = object.create( three.interpolant.prototype ); gltfcubicsplineinterpolant.prototype.constructor = gltfcubicsplineinterpolant; gltfcubicsplineinterpolant.prototype.copysamplevalue_ = function ( index ) { // copies a sample value to the result buffer. see description of gltf // cubicspline values layout in interpolate_() function below. var result = this.resultbuffer, values = this.samplevalues, valuesize = this.valuesize, offset = index * valuesize * 3 + valuesize; for ( var i = 0; i !== valuesize; i ++ ) { result[ i ] = values[ offset + i ]; } return result; }; gltfcubicsplineinterpolant.prototype.beforestart_ = gltfcubicsplineinterpolant.prototype.copysamplevalue_; gltfcubicsplineinterpolant.prototype.afterend_ = gltfcubicsplineinterpolant.prototype.copysamplevalue_; gltfcubicsplineinterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { var result = this.resultbuffer; var values = this.samplevalues; var stride = this.valuesize; var stride2 = stride * 2; var stride3 = stride * 3; var td = t1 - t0; var p = ( t - t0 ) / td; var pp = p * p; var ppp = pp * p; var offset1 = i1 * stride3; var offset0 = offset1 - stride3; var s2 = - 2 * ppp + 3 * pp; var s3 = ppp - pp; var s0 = 1 - s2; var s1 = s3 - pp + p; // layout of keyframe output values for cubicspline animations: // [ intangent_1, splinevertex_1, outtangent_1, intangent_2, splinevertex_2, ... ] for ( var i = 0; i !== stride; i ++ ) { var p0 = values[ offset0 + i + stride ]; // splinevertex_k var m0 = values[ offset0 + i + stride2 ] * td; // outtangent_k * (t_k+1 - t_k) var p1 = values[ offset1 + i + stride ]; // splinevertex_k+1 var m1 = values[ offset1 + i ] * td; // intangent_k+1 * (t_k+1 - t_k) result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; } return result; }; /*********************************/ /********** 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_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_type_sizes = { 'scalar': 1, 'vec2': 2, 'vec3': 3, 'vec4': 4, 'mat2': 4, 'mat3': 9, 'mat4': 16 }; var attributes = { position: 'position', normal: 'normal', tangent: 'tangent', texcoord_0: 'uv', texcoord_1: 'uv2', color_0: 'color', weights_0: 'skinweight', joints_0: 'skinindex', }; var path_properties = { scale: 'scale', translation: 'position', rotation: 'quaternion', weights: 'morphtargetinfluences' }; var interpolation = { cubicspline: undefined, // we use a custom interpolant (gltfcubicsplineinterpolation) for cubicspline tracks. each // keyframe track will be initialized with a default interpolation type, then modified. linear: three.interpolatelinear, step: three.interpolatediscrete }; var alpha_modes = { opaque: 'opaque', mask: 'mask', blend: 'blend' }; /* utility functions */ function resolveurl( url, path ) { // invalid url if ( typeof url !== 'string' || url === '' ) return ''; // host relative url if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); } // 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; } /** * specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#default-material */ function createdefaultmaterial( cache ) { if ( cache[ 'defaultmaterial' ] === undefined ) { cache[ 'defaultmaterial' ] = new three.meshstandardmaterial( { color: 0xffffff, emissive: 0x000000, metalness: 1, roughness: 1, transparent: false, depthtest: true, side: three.frontside } ); } return cache[ 'defaultmaterial' ]; } function addunknownextensionstouserdata( knownextensions, object, objectdef ) { // add unknown gltf extensions to an object's userdata. for ( var name in objectdef.extensions ) { if ( knownextensions[ name ] === undefined ) { object.userdata.gltfextensions = object.userdata.gltfextensions || {}; object.userdata.gltfextensions[ name ] = objectdef.extensions[ name ]; } } } /** * @param {three.object3d|three.material|three.buffergeometry} object * @param {gltf.definition} gltfdef */ function assignextrastouserdata( object, gltfdef ) { if ( gltfdef.extras !== undefined ) { if ( typeof gltfdef.extras === 'object' ) { object.assign( object.userdata, gltfdef.extras ); } else { console.warn( 'three.gltfloader: ignoring primitive type .extras, ' + gltfdef.extras ); } } } /** * specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#morph-targets * * @param {three.buffergeometry} geometry * @param {array} targets * @param {gltfparser} parser * @return {promise} */ function addmorphtargets( geometry, targets, parser ) { var hasmorphposition = false; var hasmorphnormal = false; for ( var i = 0, il = targets.length; i < il; i ++ ) { var target = targets[ i ]; if ( target.position !== undefined ) hasmorphposition = true; if ( target.normal !== undefined ) hasmorphnormal = true; if ( hasmorphposition && hasmorphnormal ) break; } if ( ! hasmorphposition && ! hasmorphnormal ) return promise.resolve( geometry ); var pendingpositionaccessors = []; var pendingnormalaccessors = []; for ( var i = 0, il = targets.length; i < il; i ++ ) { var target = targets[ i ]; if ( hasmorphposition ) { var pendingaccessor = target.position !== undefined ? parser.getdependency( 'accessor', target.position ) : geometry.attributes.position; pendingpositionaccessors.push( pendingaccessor ); } if ( hasmorphnormal ) { var pendingaccessor = target.normal !== undefined ? parser.getdependency( 'accessor', target.normal ) : geometry.attributes.normal; pendingnormalaccessors.push( pendingaccessor ); } } return promise.all( [ promise.all( pendingpositionaccessors ), promise.all( pendingnormalaccessors ) ] ).then( function ( accessors ) { var morphpositions = accessors[ 0 ]; var morphnormals = accessors[ 1 ]; if ( hasmorphposition ) geometry.morphattributes.position = morphpositions; if ( hasmorphnormal ) geometry.morphattributes.normal = morphnormals; geometry.morphtargetsrelative = true; return geometry; } ); } /** * @param {three.mesh} mesh * @param {gltf.mesh} meshdef */ function updatemorphtargets( mesh, meshdef ) { mesh.updatemorphtargets(); if ( meshdef.weights !== undefined ) { for ( var i = 0, il = meshdef.weights.length; i < il; i ++ ) { mesh.morphtargetinfluences[ i ] = meshdef.weights[ i ]; } } // .extras has user-defined data, so check that .extras.targetnames is an array. if ( meshdef.extras && array.isarray( meshdef.extras.targetnames ) ) { var targetnames = meshdef.extras.targetnames; if ( mesh.morphtargetinfluences.length === targetnames.length ) { mesh.morphtargetdictionary = {}; for ( var i = 0, il = targetnames.length; i < il; i ++ ) { mesh.morphtargetdictionary[ targetnames[ i ] ] = i; } } else { console.warn( 'three.gltfloader: invalid extras.targetnames length. ignoring names.' ); } } } function createprimitivekey( primitivedef ) { var dracoextension = primitivedef.extensions && primitivedef.extensions[ extensions.khr_draco_mesh_compression ]; var geometrykey; if ( dracoextension ) { geometrykey = 'draco:' + dracoextension.bufferview + ':' + dracoextension.indices + ':' + createattributeskey( dracoextension.attributes ); } else { geometrykey = primitivedef.indices + ':' + createattributeskey( primitivedef.attributes ) + ':' + primitivedef.mode; } return geometrykey; } function createattributeskey( attributes ) { var attributeskey = ''; var keys = object.keys( attributes ).sort(); for ( var i = 0, il = keys.length; i < il; i ++ ) { attributeskey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; } return attributeskey; } /* gltf parser */ function gltfparser( json, options ) { this.json = json || {}; this.extensions = {}; this.plugins = {}; this.options = options || {}; // loader object cache this.cache = new gltfregistry(); // associations between three.js objects and gltf elements this.associations = new map(); // buffergeometry caching this.primitivecache = {}; // object3d instance caches this.meshcache = { refs: {}, uses: {} }; this.cameracache = { refs: {}, uses: {} }; this.lightcache = { refs: {}, uses: {} }; // track node names, to ensure no duplicates this.nodenamesused = {}; // use an imagebitmaploader if imagebitmaps are supported. moves much of the // expensive work of uploading a texture to the gpu off the main thread. if ( typeof createimagebitmap !== 'undefined' && /firefox/.test( navigator.useragent ) === false ) { this.textureloader = new three.imagebitmaploader( this.options.manager ); } else { this.textureloader = new three.textureloader( this.options.manager ); } this.textureloader.setcrossorigin( this.options.crossorigin ); this.fileloader = new three.fileloader( this.options.manager ); this.fileloader.setresponsetype( 'arraybuffer' ); if ( this.options.crossorigin === 'use-credentials' ) { this.fileloader.setwithcredentials( true ); } } gltfparser.prototype.setextensions = function ( extensions ) { this.extensions = extensions; }; gltfparser.prototype.setplugins = function ( plugins ) { this.plugins = plugins; }; gltfparser.prototype.parse = function ( onload, onerror ) { var parser = this; var json = this.json; var extensions = this.extensions; // clear the loader cache this.cache.removeall(); // mark the special nodes/meshes in json for efficient parse this._invokeall( function ( ext ) { return ext._markdefs && ext._markdefs(); } ); promise.all( [ this.getdependencies( 'scene' ), this.getdependencies( 'animation' ), this.getdependencies( 'camera' ), ] ).then( function ( dependencies ) { var result = { scene: dependencies[ 0 ][ json.scene || 0 ], scenes: dependencies[ 0 ], animations: dependencies[ 1 ], cameras: dependencies[ 2 ], asset: json.asset, parser: parser, userdata: {} }; addunknownextensionstouserdata( extensions, result, json ); assignextrastouserdata( result, json ); onload( result ); } ).catch( onerror ); }; /** * marks the special nodes/meshes in json for efficient parse. */ gltfparser.prototype._markdefs = function () { var nodedefs = this.json.nodes || []; var skindefs = this.json.skins || []; var meshdefs = this.json.meshes || []; // nothing in the node definition indicates whether it is a bone or an // object3d. use the skins' joint references to mark bones. for ( var skinindex = 0, skinlength = skindefs.length; skinindex < skinlength; skinindex ++ ) { var joints = skindefs[ skinindex ].joints; for ( var i = 0, il = joints.length; i < il; i ++ ) { nodedefs[ joints[ i ] ].isbone = true; } } // iterate over all nodes, marking references to shared resources, // as well as skeleton joints. for ( var nodeindex = 0, nodelength = nodedefs.length; nodeindex < nodelength; nodeindex ++ ) { var nodedef = nodedefs[ nodeindex ]; if ( nodedef.mesh !== undefined ) { this._addnoderef( this.meshcache, nodedef.mesh ); // nothing in the mesh definition indicates whether it is // a skinnedmesh or mesh. use the node's mesh reference // to mark skinnedmesh if node has skin. if ( nodedef.skin !== undefined ) { meshdefs[ nodedef.mesh ].isskinnedmesh = true; } } if ( nodedef.camera !== undefined ) { this._addnoderef( this.cameracache, nodedef.camera ); } } }; /** * counts references to shared node / object3d resources. these resources * can be reused, or "instantiated", at multiple nodes in the scene * hierarchy. mesh, camera, and light instances are instantiated and must * be marked. non-scenegraph resources (like materials, geometries, and * textures) can be reused directly and are not marked here. * * example: cesiummilktruck sample model reuses "wheel" meshes. */ gltfparser.prototype._addnoderef = function ( cache, index ) { if ( index === undefined ) return; if ( cache.refs[ index ] === undefined ) { cache.refs[ index ] = cache.uses[ index ] = 0; } cache.refs[ index ] ++; }; /** returns a reference to a shared resource, cloning it if necessary. */ gltfparser.prototype._getnoderef = function ( cache, index, object ) { if ( cache.refs[ index ] <= 1 ) return object; var ref = object.clone(); ref.name += '_instance_' + ( cache.uses[ index ] ++ ); return ref; }; gltfparser.prototype._invokeone = function ( func ) { var extensions = object.values( this.plugins ); extensions.push( this ); for ( var i = 0; i < extensions.length; i ++ ) { var result = func( extensions[ i ] ); if ( result ) return result; } }; gltfparser.prototype._invokeall = function ( func ) { var extensions = object.values( this.plugins ); extensions.unshift( this ); var pending = []; for ( var i = 0; i < extensions.length; i ++ ) { var result = func( extensions[ i ] ); if ( result ) pending.push( result ); } return pending; }; /** * requests the specified dependency asynchronously, with caching. * @param {string} type * @param {number} index * @return {promise} */ gltfparser.prototype.getdependency = function ( type, index ) { var cachekey = type + ':' + index; var dependency = this.cache.get( cachekey ); if ( ! dependency ) { switch ( type ) { case 'scene': dependency = this.loadscene( index ); break; case 'node': dependency = this.loadnode( index ); break; case 'mesh': dependency = this._invokeone( function ( ext ) { return ext.loadmesh && ext.loadmesh( index ); } ); break; case 'accessor': dependency = this.loadaccessor( index ); break; case 'bufferview': dependency = this._invokeone( function ( ext ) { return ext.loadbufferview && ext.loadbufferview( index ); } ); break; case 'buffer': dependency = this.loadbuffer( index ); break; case 'material': dependency = this._invokeone( function ( ext ) { return ext.loadmaterial && ext.loadmaterial( index ); } ); break; case 'texture': dependency = this._invokeone( function ( ext ) { return ext.loadtexture && ext.loadtexture( index ); } ); break; case 'skin': dependency = this.loadskin( index ); break; case 'animation': dependency = this.loadanimation( index ); break; case 'camera': dependency = this.loadcamera( index ); break; default: throw new error( 'unknown type: ' + type ); } this.cache.add( cachekey, dependency ); } return dependency; }; /** * requests all dependencies of the specified type asynchronously, with caching. * @param {string} type * @return {promise>} */ gltfparser.prototype.getdependencies = function ( type ) { var dependencies = this.cache.get( type ); if ( ! dependencies ) { var parser = this; var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; dependencies = promise.all( defs.map( function ( def, index ) { return parser.getdependency( type, index ); } ) ); this.cache.add( type, dependencies ); } return dependencies; }; /** * specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#buffers-and-buffer-views * @param {number} bufferindex * @return {promise} */ gltfparser.prototype.loadbuffer = function ( bufferindex ) { var bufferdef = this.json.buffers[ bufferindex ]; var loader = this.fileloader; if ( bufferdef.type && bufferdef.type !== 'arraybuffer' ) { throw new error( 'three.gltfloader: ' + bufferdef.type + ' buffer type is not supported.' ); } // if present, glb container is required to be the first buffer. if ( bufferdef.uri === undefined && bufferindex === 0 ) { return promise.resolve( this.extensions[ extensions.khr_binary_gltf ].body ); } var options = this.options; return new promise( function ( resolve, reject ) { loader.load( resolveurl( bufferdef.uri, options.path ), resolve, undefined, function () { reject( new error( 'three.gltfloader: failed to load buffer "' + bufferdef.uri + '".' ) ); } ); } ); }; /** * specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#buffers-and-buffer-views * @param {number} bufferviewindex * @return {promise} */ 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 ); } ); }; /** * specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#accessors * @param {number} accessorindex * @return {promise} */ gltfparser.prototype.loadaccessor = function ( accessorindex ) { var parser = this; var json = this.json; var accessordef = this.json.accessors[ accessorindex ]; if ( accessordef.bufferview === undefined && accessordef.sparse === undefined ) { // ignore empty accessors, which may be used to declare runtime // information about attributes coming from another source (e.g. draco // compression extension). return promise.resolve( null ); } var pendingbufferviews = []; if ( accessordef.bufferview !== undefined ) { pendingbufferviews.push( this.getdependency( 'bufferview', accessordef.bufferview ) ); } else { pendingbufferviews.push( null ); } if ( accessordef.sparse !== undefined ) { pendingbufferviews.push( this.getdependency( 'bufferview', accessordef.sparse.indices.bufferview ) ); pendingbufferviews.push( this.getdependency( 'bufferview', accessordef.sparse.values.bufferview ) ); } return promise.all( pendingbufferviews ).then( function ( bufferviews ) { var bufferview = bufferviews[ 0 ]; var itemsize = webgl_type_sizes[ accessordef.type ]; var typedarray = webgl_component_types[ accessordef.componenttype ]; // for vec3: itemsize is 3, elementbytes is 4, itembytes is 12. var elementbytes = typedarray.bytes_per_element; var itembytes = elementbytes * itemsize; var byteoffset = accessordef.byteoffset || 0; var bytestride = accessordef.bufferview !== undefined ? json.bufferviews[ accessordef.bufferview ].bytestride : undefined; var normalized = accessordef.normalized === true; var array, bufferattribute; // the buffer is not interleaved if the stride is the item size in bytes. if ( bytestride && bytestride !== itembytes ) { // each "slice" of the buffer, as defined by 'count' elements of 'bytestride' bytes, gets its own interleavedbuffer // this makes sure that iba.count reflects accessor.count properly var ibslice = math.floor( byteoffset / bytestride ); var ibcachekey = 'interleavedbuffer:' + accessordef.bufferview + ':' + accessordef.componenttype + ':' + ibslice + ':' + accessordef.count; var ib = parser.cache.get( ibcachekey ); if ( ! ib ) { array = new typedarray( bufferview, ibslice * bytestride, accessordef.count * bytestride / elementbytes ); // integer parameters to ib/iba are in array elements, not bytes. ib = new three.interleavedbuffer( array, bytestride / elementbytes ); parser.cache.add( ibcachekey, ib ); } bufferattribute = new three.interleavedbufferattribute( ib, itemsize, ( byteoffset % bytestride ) / elementbytes, normalized ); } else { if ( bufferview === null ) { array = new typedarray( accessordef.count * itemsize ); } else { array = new typedarray( bufferview, byteoffset, accessordef.count * itemsize ); } bufferattribute = new three.bufferattribute( array, itemsize, normalized ); } // https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#sparse-accessors if ( accessordef.sparse !== undefined ) { var itemsizeindices = webgl_type_sizes.scalar; var typedarrayindices = webgl_component_types[ accessordef.sparse.indices.componenttype ]; var byteoffsetindices = accessordef.sparse.indices.byteoffset || 0; var byteoffsetvalues = accessordef.sparse.values.byteoffset || 0; var sparseindices = new typedarrayindices( bufferviews[ 1 ], byteoffsetindices, accessordef.sparse.count * itemsizeindices ); var sparsevalues = new typedarray( bufferviews[ 2 ], byteoffsetvalues, accessordef.sparse.count * itemsize ); if ( bufferview !== null ) { // avoid modifying the original arraybuffer, if the bufferview wasn't initialized with zeroes. bufferattribute = new three.bufferattribute( bufferattribute.array.slice(), bufferattribute.itemsize, bufferattribute.normalized ); } for ( var i = 0, il = sparseindices.length; i < il; i ++ ) { var index = sparseindices[ i ]; bufferattribute.setx( index, sparsevalues[ i * itemsize ] ); if ( itemsize >= 2 ) bufferattribute.sety( index, sparsevalues[ i * itemsize + 1 ] ); if ( itemsize >= 3 ) bufferattribute.setz( index, sparsevalues[ i * itemsize + 2 ] ); if ( itemsize >= 4 ) bufferattribute.setw( index, sparsevalues[ i * itemsize + 3 ] ); if ( itemsize >= 5 ) throw new error( 'three.gltfloader: unsupported itemsize in sparse bufferattribute.' ); } } return bufferattribute; } ); }; /** * specification: https://github.com/khronosgroup/gltf/tree/master/specification/2.0#textures * @param {number} textureindex * @return {promise} */ gltfparser.prototype.loadtexture = function ( textureindex ) { var parser = this; var json = this.json; var options = this.options; var texturedef = json.textures[ textureindex ]; var textureextensions = texturedef.extensions || {}; var source; if ( textureextensions[ extensions.msft_texture_dds ] ) { source = json.images[ textureextensions[ extensions.msft_texture_dds ].source ]; } else { source = json.images[ texturedef.source ]; } var loader; if ( source.uri ) { loader = options.manager.gethandler( source.uri ); } if ( ! loader ) { loader = textureextensions[ extensions.msft_texture_dds ] ? parser.extensions[ extensions.msft_texture_dds ].ddsloader : this.textureloader; } return this.loadtextureimage( textureindex, source, loader ); }; gltfparser.prototype.loadtextureimage = function ( textureindex, source, loader ) { var parser = this; var json = this.json; var options = this.options; var texturedef = json.textures[ textureindex ]; var url = self.url || self.webkiturl; var sourceuri = source.uri; var isobjecturl = false; var hasalpha = true; if ( source.mimetype === 'image/jpeg' ) hasalpha = false; if ( source.bufferview !== undefined ) { // load binary image data from bufferview, if provided. sourceuri = parser.getdependency( 'bufferview', source.bufferview ).then( function ( bufferview ) { if ( source.mimetype === 'image/png' ) { // inspect the png 'ihdr' chunk to determine whether the image could have an // alpha channel. this check is conservative — the image could have an alpha // channel with all values == 1, and the indexed type (colortype == 3) only // sometimes contains alpha. // // https://en.wikipedia.org/wiki/portable_network_graphics#file_header var colortype = new dataview( bufferview, 25, 1 ).getuint8( 0, false ); hasalpha = colortype === 6 || colortype === 4 || colortype === 3; } isobjecturl = true; var blob = new blob( [ bufferview ], { type: source.mimetype } ); sourceuri = url.createobjecturl( blob ); return sourceuri; } ); } return promise.resolve( sourceuri ).then( function ( sourceuri ) { return new promise( function ( resolve, reject ) { var onload = resolve; if ( loader.isimagebitmaploader === true ) { onload = function ( imagebitmap ) { resolve( new three.canvastexture( imagebitmap ) ); }; } loader.load( resolveurl( sourceuri, options.path ), onload, undefined, reject ); } ); } ).then( function ( texture ) { // clean up resources and configure texture. if ( isobjecturl === true ) { url.revokeobjecturl( sourceuri ); } texture.flipy = false; if ( texturedef.name ) texture.name = texturedef.name; // when there is definitely no alpha channel in the texture, set rgbformat to save space. if ( ! hasalpha ) texture.format = three.rgbformat; 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; parser.associations.set( texture, { type: 'textures', index: textureindex } ); return texture; } ); }; /** * asynchronously assigns a texture to the given material parameters. * @param {object} materialparams * @param {string} mapname * @param {object} mapdef * @return {promise} */ gltfparser.prototype.assigntexture = function ( materialparams, mapname, mapdef ) { var parser = this; return this.getdependency( 'texture', mapdef.index ).then( function ( texture ) { // materials sample aomap from uv set 1 and other maps from uv set 0 - this can't be configured // however, we will copy uv set 0 to uv set 1 on demand for aomap if ( mapdef.texcoord !== undefined && mapdef.texcoord != 0 && ! ( mapname === 'aomap' && mapdef.texcoord == 1 ) ) { console.warn( 'three.gltfloader: custom uv set ' + mapdef.texcoord + ' for texture ' + mapname + ' not yet supported.' ); } if ( parser.extensions[ extensions.khr_texture_transform ] ) { var transform = mapdef.extensions !== undefined ? mapdef.extensions[ extensions.khr_texture_transform ] : undefined; if ( transform ) { var gltfreference = parser.associations.get( texture ); texture = parser.extensions[ extensions.khr_texture_transform ].extendtexture( texture, transform ); parser.associations.set( texture, gltfreference ); } } materialparams[ mapname ] = texture; } ); }; /** * assigns final material to a mesh, line, or points instance. the instance * already has a material (generated from the gltf material options alone) * but reuse of the same gltf material may require multiple threejs materials * to accomodate different primitive types, defines, etc. new materials will * be created if necessary, and reused from a cache. * @param {three.object3d} mesh mesh, line, or points instance. */ gltfparser.prototype.assignfinalmaterial = function ( mesh ) { var geometry = mesh.geometry; var material = mesh.material; var usevertextangents = geometry.attributes.tangent !== undefined; var usevertexcolors = geometry.attributes.color !== undefined; var useflatshading = geometry.attributes.normal === undefined; var useskinning = mesh.isskinnedmesh === true; var usemorphtargets = object.keys( geometry.morphattributes ).length > 0; var usemorphnormals = usemorphtargets && geometry.morphattributes.normal !== undefined; if ( mesh.ispoints ) { var cachekey = 'pointsmaterial:' + material.uuid; var pointsmaterial = this.cache.get( cachekey ); if ( ! pointsmaterial ) { pointsmaterial = new three.pointsmaterial(); three.material.prototype.copy.call( pointsmaterial, material ); pointsmaterial.color.copy( material.color ); pointsmaterial.map = material.map; pointsmaterial.sizeattenuation = false; // gltf spec says points should be 1px this.cache.add( cachekey, pointsmaterial ); } material = pointsmaterial; } else if ( mesh.isline ) { var cachekey = 'linebasicmaterial:' + material.uuid; var linematerial = this.cache.get( cachekey ); if ( ! linematerial ) { linematerial = new three.linebasicmaterial(); three.material.prototype.copy.call( linematerial, material ); linematerial.color.copy( material.color ); this.cache.add( cachekey, linematerial ); } material = linematerial; } // clone the material if it will be modified if ( usevertextangents || usevertexcolors || useflatshading || useskinning || usemorphtargets ) { var cachekey = 'clonedmaterial:' + material.uuid + ':'; if ( material.isgltfspecularglossinessmaterial ) cachekey += 'specular-glossiness:'; if ( useskinning ) cachekey += 'skinning:'; if ( usevertextangents ) cachekey += 'vertex-tangents:'; if ( usevertexcolors ) cachekey += 'vertex-colors:'; if ( useflatshading ) cachekey += 'flat-shading:'; if ( usemorphtargets ) cachekey += 'morph-targets:'; if ( usemorphnormals ) cachekey += 'morph-normals:'; var cachedmaterial = this.cache.get( cachekey ); if ( ! cachedmaterial ) { cachedmaterial = material.clone(); if ( useskinning ) cachedmaterial.skinning = true; if ( usevertextangents ) cachedmaterial.vertextangents = true; if ( usevertexcolors ) cachedmaterial.vertexcolors = true; if ( useflatshading ) cachedmaterial.flatshading = true; if ( usemorphtargets ) cachedmaterial.morphtargets = true; if ( usemorphnormals ) cachedmaterial.morphnormals = true; this.cache.add( cachekey, cachedmaterial ); this.associations.set( cachedmaterial, this.associations.get( material ) ); } material = cachedmaterial; } // workarounds for mesh and geometry if ( material.aomap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) { geometry.setattribute( 'uv2', geometry.attributes.uv ); } // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 if ( material.normalscale && ! usevertextangents ) { material.normalscale.y = - material.normalscale.y; } if ( material.clearcoatnormalscale && ! usevertextangents ) { material.clearcoatnormalscale.y = - material.clearcoatnormalscale.y; } mesh.material = material; }; gltfparser.prototype.getmaterialtype = function ( /* materialindex */ ) { return three.meshstandardmaterial; }; /** * specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#materials * @param {number} materialindex * @return {promise} */ gltfparser.prototype.loadmaterial = function ( materialindex ) { var parser = this; var json = this.json; var extensions = this.extensions; var materialdef = json.materials[ materialindex ]; var materialtype; var materialparams = {}; var materialextensions = materialdef.extensions || {}; var pending = []; if ( materialextensions[ extensions.khr_materials_pbr_specular_glossiness ] ) { var sgextension = extensions[ extensions.khr_materials_pbr_specular_glossiness ]; materialtype = sgextension.getmaterialtype(); pending.push( sgextension.extendparams( materialparams, materialdef, parser ) ); } else if ( materialextensions[ extensions.khr_materials_unlit ] ) { var kmuextension = extensions[ extensions.khr_materials_unlit ]; materialtype = kmuextension.getmaterialtype(); pending.push( kmuextension.extendparams( materialparams, materialdef, parser ) ); } else { // specification: // https://github.com/khronosgroup/gltf/tree/master/specification/2.0#metallic-roughness-material var metallicroughness = materialdef.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 ) ); } materialparams.metalness = metallicroughness.metallicfactor !== undefined ? metallicroughness.metallicfactor : 1.0; materialparams.roughness = metallicroughness.roughnessfactor !== undefined ? metallicroughness.roughnessfactor : 1.0; if ( metallicroughness.metallicroughnesstexture !== undefined ) { pending.push( parser.assigntexture( materialparams, 'metalnessmap', metallicroughness.metallicroughnesstexture ) ); pending.push( parser.assigntexture( materialparams, 'roughnessmap', metallicroughness.metallicroughnesstexture ) ); } materialtype = this._invokeone( function ( ext ) { return ext.getmaterialtype && ext.getmaterialtype( materialindex ); } ); pending.push( promise.all( this._invokeall( function ( ext ) { return ext.extendmaterialparams && ext.extendmaterialparams( materialindex, materialparams ); } ) ) ); } if ( materialdef.doublesided === true ) { materialparams.side = three.doubleside; } var alphamode = materialdef.alphamode || alpha_modes.opaque; if ( alphamode === alpha_modes.blend ) { materialparams.transparent = true; // see: https://github.com/mrdoob/three.js/issues/17706 materialparams.depthwrite = false; } else { materialparams.transparent = false; if ( alphamode === alpha_modes.mask ) { materialparams.alphatest = materialdef.alphacutoff !== undefined ? materialdef.alphacutoff : 0.5; } } if ( materialdef.normaltexture !== undefined && materialtype !== three.meshbasicmaterial ) { pending.push( parser.assigntexture( materialparams, 'normalmap', materialdef.normaltexture ) ); materialparams.normalscale = new three.vector2( 1, 1 ); if ( materialdef.normaltexture.scale !== undefined ) { materialparams.normalscale.set( materialdef.normaltexture.scale, materialdef.normaltexture.scale ); } } if ( materialdef.occlusiontexture !== undefined && materialtype !== three.meshbasicmaterial ) { pending.push( parser.assigntexture( materialparams, 'aomap', materialdef.occlusiontexture ) ); if ( materialdef.occlusiontexture.strength !== undefined ) { materialparams.aomapintensity = materialdef.occlusiontexture.strength; } } if ( materialdef.emissivefactor !== undefined && materialtype !== three.meshbasicmaterial ) { materialparams.emissive = new three.color().fromarray( materialdef.emissivefactor ); } if ( materialdef.emissivetexture !== undefined && materialtype !== three.meshbasicmaterial ) { pending.push( parser.assigntexture( materialparams, 'emissivemap', materialdef.emissivetexture ) ); } return promise.all( pending ).then( function () { var material; if ( materialtype === gltfmeshstandardsgmaterial ) { material = extensions[ extensions.khr_materials_pbr_specular_glossiness ].creatematerial( materialparams ); } else { material = new materialtype( materialparams ); } if ( materialdef.name ) material.name = materialdef.name; // basecolortexture, emissivetexture, and specularglossinesstexture use srgb encoding. if ( material.map ) material.map.encoding = three.srgbencoding; if ( material.emissivemap ) material.emissivemap.encoding = three.srgbencoding; assignextrastouserdata( material, materialdef ); parser.associations.set( material, { type: 'materials', index: materialindex } ); if ( materialdef.extensions ) addunknownextensionstouserdata( extensions, material, materialdef ); return material; } ); }; /** when object3d instances are targeted by animation, they need unique names. */ gltfparser.prototype.createuniquename = function ( originalname ) { var name = three.propertybinding.sanitizenodename( originalname || '' ); for ( var i = 1; this.nodenamesused[ name ]; ++ i ) { name = originalname + '_' + i; } this.nodenamesused[ name ] = true; return name; }; /** * @param {three.buffergeometry} geometry * @param {gltf.primitive} primitivedef * @param {gltfparser} parser */ function computebounds( geometry, primitivedef, parser ) { var attributes = primitivedef.attributes; var box = new three.box3(); if ( attributes.position !== undefined ) { var accessor = parser.json.accessors[ attributes.position ]; var min = accessor.min; var max = accessor.max; // gltf requires 'min' and 'max', but vrm (which extends gltf) currently ignores that requirement. if ( min !== undefined && max !== undefined ) { box.set( new three.vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), new three.vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) ); } else { console.warn( 'three.gltfloader: missing min/max properties for accessor position.' ); return; } } else { return; } var targets = primitivedef.targets; if ( targets !== undefined ) { var maxdisplacement = new three.vector3(); var vector = new three.vector3(); for ( var i = 0, il = targets.length; i < il; i ++ ) { var target = targets[ i ]; if ( target.position !== undefined ) { var accessor = parser.json.accessors[ target.position ]; var min = accessor.min; var max = accessor.max; // gltf requires 'min' and 'max', but vrm (which extends gltf) currently ignores that requirement. if ( min !== undefined && max !== undefined ) { // we need to get max of absolute components because target weight is [-1,1] vector.setx( math.max( math.abs( min[ 0 ] ), math.abs( max[ 0 ] ) ) ); vector.sety( math.max( math.abs( min[ 1 ] ), math.abs( max[ 1 ] ) ) ); vector.setz( math.max( math.abs( min[ 2 ] ), math.abs( max[ 2 ] ) ) ); // note: this assumes that the sum of all weights is at most 1. this isn't quite correct - it's more conservative // to assume that each target can have a max weight of 1. however, for some use cases - notably, when morph targets // are used to implement key-frame animations and as such only two are active at a time - this results in very large // boxes. so for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. maxdisplacement.max( vector ); } else { console.warn( 'three.gltfloader: missing min/max properties for accessor position.' ); } } } // as per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. box.expandbyvector( maxdisplacement ); } geometry.boundingbox = box; var sphere = new three.sphere(); box.getcenter( sphere.center ); sphere.radius = box.min.distanceto( box.max ) / 2; geometry.boundingsphere = sphere; } /** * @param {three.buffergeometry} geometry * @param {gltf.primitive} primitivedef * @param {gltfparser} parser * @return {promise} */ function addprimitiveattributes( geometry, primitivedef, parser ) { var attributes = primitivedef.attributes; var pending = []; function assignattributeaccessor( accessorindex, attributename ) { return parser.getdependency( 'accessor', accessorindex ) .then( function ( accessor ) { geometry.setattribute( attributename, accessor ); } ); } for ( var gltfattributename in attributes ) { var threeattributename = attributes[ gltfattributename ] || gltfattributename.tolowercase(); // skip attributes already provided by e.g. draco extension. if ( threeattributename in geometry.attributes ) continue; pending.push( assignattributeaccessor( attributes[ gltfattributename ], threeattributename ) ); } if ( primitivedef.indices !== undefined && ! geometry.index ) { var accessor = parser.getdependency( 'accessor', primitivedef.indices ).then( function ( accessor ) { geometry.setindex( accessor ); } ); pending.push( accessor ); } assignextrastouserdata( geometry, primitivedef ); computebounds( geometry, primitivedef, parser ); return promise.all( pending ).then( function () { return primitivedef.targets !== undefined ? addmorphtargets( geometry, primitivedef.targets, parser ) : geometry; } ); } /** * @param {three.buffergeometry} geometry * @param {number} drawmode * @return {three.buffergeometry} */ function totrianglesdrawmode( geometry, drawmode ) { var index = geometry.getindex(); // generate index if not present if ( index === null ) { var indices = []; var position = geometry.getattribute( 'position' ); if ( position !== undefined ) { for ( var i = 0; i < position.count; i ++ ) { indices.push( i ); } geometry.setindex( indices ); index = geometry.getindex(); } else { console.error( 'three.gltfloader.totrianglesdrawmode(): undefined position attribute. processing not possible.' ); return geometry; } } // var numberoftriangles = index.count - 2; var newindices = []; if ( drawmode === three.trianglefandrawmode ) { // gl.triangle_fan for ( var i = 1; i <= numberoftriangles; i ++ ) { newindices.push( index.getx( 0 ) ); newindices.push( index.getx( i ) ); newindices.push( index.getx( i + 1 ) ); } } else { // gl.triangle_strip for ( var i = 0; i < numberoftriangles; i ++ ) { if ( i % 2 === 0 ) { newindices.push( index.getx( i ) ); newindices.push( index.getx( i + 1 ) ); newindices.push( index.getx( i + 2 ) ); } else { newindices.push( index.getx( i + 2 ) ); newindices.push( index.getx( i + 1 ) ); newindices.push( index.getx( i ) ); } } } if ( ( newindices.length / 3 ) !== numberoftriangles ) { console.error( 'three.gltfloader.totrianglesdrawmode(): unable to generate correct amount of triangles.' ); } // build final geometry var newgeometry = geometry.clone(); newgeometry.setindex( newindices ); return newgeometry; } /** * specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#geometry * * creates buffergeometries from primitives. * * @param {array} primitives * @return {promise>} */ gltfparser.prototype.loadgeometries = function ( primitives ) { var parser = this; var extensions = this.extensions; var cache = this.primitivecache; function createdracoprimitive( primitive ) { return extensions[ extensions.khr_draco_mesh_compression ] .decodeprimitive( primitive, parser ) .then( function ( geometry ) { return addprimitiveattributes( geometry, primitive, parser ); } ); } var pending = []; for ( var i = 0, il = primitives.length; i < il; i ++ ) { var primitive = primitives[ i ]; var cachekey = createprimitivekey( primitive ); // see if we've already created this geometry var cached = cache[ cachekey ]; if ( cached ) { // use the cached geometry if it exists pending.push( cached.promise ); } else { var geometrypromise; if ( primitive.extensions && primitive.extensions[ extensions.khr_draco_mesh_compression ] ) { // use draco geometry if available geometrypromise = createdracoprimitive( primitive ); } else { // otherwise create a new geometry geometrypromise = addprimitiveattributes( new three.buffergeometry(), primitive, parser ); } // cache this geometry cache[ cachekey ] = { primitive: primitive, promise: geometrypromise }; pending.push( geometrypromise ); } } return promise.all( pending ); }; /** * specification: https://github.com/khronosgroup/gltf/blob/master/specification/2.0/readme.md#meshes * @param {number} meshindex * @return {promise} */ gltfparser.prototype.loadmesh = function ( meshindex ) { var parser = this; var json = this.json; var extensions = this.extensions; var meshdef = json.meshes[ meshindex ]; var primitives = meshdef.primitives; var pending = []; for ( var i = 0, il = primitives.length; i < il; i ++ ) { var material = primitives[ i ].material === undefined ? createdefaultmaterial( this.cache ) : this.getdependency( 'material', primitives[ i ].material ); pending.push( material ); } pending.push( parser.loadgeometries( primitives ) ); return promise.all( pending ).then( function ( results ) { var materials = results.slice( 0, results.length - 1 ); var geometries = results[ results.length - 1 ]; var meshes = []; for ( var i = 0, il = geometries.length; i < il; i ++ ) { var geometry = geometries[ i ]; var primitive = primitives[ i ]; // 1. create mesh var mesh; var material = materials[ i ]; if ( primitive.mode === webgl_constants.triangles || primitive.mode === webgl_constants.triangle_strip || primitive.mode === webgl_constants.triangle_fan || primitive.mode === undefined ) { // .isskinnedmesh isn't in gltf spec. see ._markdefs() mesh = meshdef.isskinnedmesh === true ? new three.skinnedmesh( geometry, material ) : new three.mesh( geometry, material ); if ( mesh.isskinnedmesh === true && ! mesh.geometry.attributes.skinweight.normalized ) { // we normalize floating point skin weight array to fix malformed assets (see #15319) // it's important to skip this for non-float32 data since normalizeskinweights assumes non-normalized inputs mesh.normalizeskinweights(); } if ( primitive.mode === webgl_constants.triangle_strip ) { mesh.geometry = totrianglesdrawmode( mesh.geometry, three.trianglestripdrawmode ); } else if ( primitive.mode === webgl_constants.triangle_fan ) { mesh.geometry = totrianglesdrawmode( mesh.geometry, 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.gltfloader: primitive mode unsupported: ' + primitive.mode ); } if ( object.keys( mesh.geometry.morphattributes ).length > 0 ) { updatemorphtargets( mesh, meshdef ); } mesh.name = parser.createuniquename( meshdef.name || ( 'mesh_' + meshindex ) ); assignextrastouserdata( mesh, meshdef ); if ( primitive.extensions ) addunknownextensionstouserdata( extensions, mesh, primitive ); parser.assignfinalmaterial( mesh ); meshes.push( mesh ); } if ( meshes.length === 1 ) { return meshes[ 0 ]; } var group = new three.group(); for ( var i = 0, il = meshes.length; i < il; i ++ ) { group.add( meshes[ i ] ); } return group; } ); }; /** * specification: https://github.com/khronosgroup/gltf/tree/master/specification/2.0#cameras * @param {number} cameraindex * @return {promise} */ gltfparser.prototype.loadcamera = function ( cameraindex ) { var camera; var cameradef = this.json.cameras[ cameraindex ]; var params = cameradef[ cameradef.type ]; if ( ! params ) { console.warn( 'three.gltfloader: missing camera parameters.' ); return; } if ( cameradef.type === 'perspective' ) { camera = new three.perspectivecamera( three.mathutils.radtodeg( params.yfov ), params.aspectratio || 1, params.znear || 1, params.zfar || 2e6 ); } else if ( cameradef.type === 'orthographic' ) { camera = new three.orthographiccamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); } if ( cameradef.name ) camera.name = this.createuniquename( cameradef.name ); assignextrastouserdata( camera, cameradef ); return promise.resolve( camera ); }; /** * specification: https://github.com/khronosgroup/gltf/tree/master/specification/2.0#skins * @param {number} skinindex * @return {promise} */ gltfparser.prototype.loadskin = function ( skinindex ) { var skindef = this.json.skins[ skinindex ]; var skinentry = { joints: skindef.joints }; if ( skindef.inversebindmatrices === undefined ) { return promise.resolve( skinentry ); } return this.getdependency( 'accessor', skindef.inversebindmatrices ).then( function ( accessor ) { skinentry.inversebindmatrices = accessor; return skinentry; } ); }; /** * specification: https://github.com/khronosgroup/gltf/tree/master/specification/2.0#animations * @param {number} animationindex * @return {promise} */ gltfparser.prototype.loadanimation = function ( animationindex ) { var json = this.json; var animationdef = json.animations[ animationindex ]; var pendingnodes = []; var pendinginputaccessors = []; var pendingoutputaccessors = []; var pendingsamplers = []; var pendingtargets = []; for ( var i = 0, il = animationdef.channels.length; i < il; i ++ ) { var channel = animationdef.channels[ i ]; var sampler = animationdef.samplers[ channel.sampler ]; var target = channel.target; var name = target.node !== undefined ? target.node : target.id; // note: target.id is deprecated. var input = animationdef.parameters !== undefined ? animationdef.parameters[ sampler.input ] : sampler.input; var output = animationdef.parameters !== undefined ? animationdef.parameters[ sampler.output ] : sampler.output; pendingnodes.push( this.getdependency( 'node', name ) ); pendinginputaccessors.push( this.getdependency( 'accessor', input ) ); pendingoutputaccessors.push( this.getdependency( 'accessor', output ) ); pendingsamplers.push( sampler ); pendingtargets.push( target ); } return promise.all( [ promise.all( pendingnodes ), promise.all( pendinginputaccessors ), promise.all( pendingoutputaccessors ), promise.all( pendingsamplers ), promise.all( pendingtargets ) ] ).then( function ( dependencies ) { var nodes = dependencies[ 0 ]; var inputaccessors = dependencies[ 1 ]; var outputaccessors = dependencies[ 2 ]; var samplers = dependencies[ 3 ]; var targets = dependencies[ 4 ]; var tracks = []; for ( var i = 0, il = nodes.length; i < il; i ++ ) { var node = nodes[ i ]; var inputaccessor = inputaccessors[ i ]; var outputaccessor = outputaccessors[ i ]; var sampler = samplers[ i ]; var target = targets[ i ]; if ( node === undefined ) continue; 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; var interpolation = sampler.interpolation !== undefined ? interpolation[ sampler.interpolation ] : three.interpolatelinear; var targetnames = []; if ( path_properties[ target.path ] === path_properties.weights ) { // node may be a three.group (gltf mesh with several primitives) or a three.mesh. node.traverse( function ( object ) { if ( object.ismesh === true && object.morphtargetinfluences ) { targetnames.push( object.name ? object.name : object.uuid ); } } ); } else { targetnames.push( targetname ); } var outputarray = outputaccessor.array; if ( outputaccessor.normalized ) { var scale; if ( outputarray.constructor === int8array ) { scale = 1 / 127; } else if ( outputarray.constructor === uint8array ) { scale = 1 / 255; } else if ( outputarray.constructor == int16array ) { scale = 1 / 32767; } else if ( outputarray.constructor === uint16array ) { scale = 1 / 65535; } else { throw new error( 'three.gltfloader: unsupported output accessor component type.' ); } var scaled = new float32array( outputarray.length ); for ( var j = 0, jl = outputarray.length; j < jl; j ++ ) { scaled[ j ] = outputarray[ j ] * scale; } outputarray = scaled; } for ( var j = 0, jl = targetnames.length; j < jl; j ++ ) { var track = new typedkeyframetrack( targetnames[ j ] + '.' + path_properties[ target.path ], inputaccessor.array, outputarray, interpolation ); // override interpolation with custom factory method. if ( sampler.interpolation === 'cubicspline' ) { track.createinterpolant = function interpolantfactorymethodgltfcubicspline( result ) { // a cubicspline keyframe in gltf has three output values for each input value, // representing intangent, splinevertex, and outtangent. as a result, track.getvaluesize() // must be divided by three to get the interpolant's samplesize argument. return new gltfcubicsplineinterpolant( this.times, this.values, this.getvaluesize() / 3, result ); }; // mark as cubicspline. `track.getinterpolation()` doesn't support custom interpolants. track.createinterpolant.isinterpolantfactorymethodgltfcubicspline = true; } tracks.push( track ); } } var name = animationdef.name ? animationdef.name : 'animation_' + animationindex; return new three.animationclip( name, undefined, tracks ); } ); }; /** * specification: https://github.com/khronosgroup/gltf/tree/master/specification/2.0#nodes-and-hierarchy * @param {number} nodeindex * @return {promise} */ gltfparser.prototype.loadnode = function ( nodeindex ) { var json = this.json; var extensions = this.extensions; var parser = this; var nodedef = json.nodes[ nodeindex ]; // reserve node's name before its dependencies, so the root has the intended name. var nodename = nodedef.name ? parser.createuniquename( nodedef.name ) : ''; return ( function () { var pending = []; if ( nodedef.mesh !== undefined ) { pending.push( parser.getdependency( 'mesh', nodedef.mesh ).then( function ( mesh ) { var node = parser._getnoderef( parser.meshcache, nodedef.mesh, mesh ); // if weights are provided on the node, override weights on the mesh. if ( nodedef.weights !== undefined ) { node.traverse( function ( o ) { if ( ! o.ismesh ) return; for ( var i = 0, il = nodedef.weights.length; i < il; i ++ ) { o.morphtargetinfluences[ i ] = nodedef.weights[ i ]; } } ); } return node; } ) ); } if ( nodedef.camera !== undefined ) { pending.push( parser.getdependency( 'camera', nodedef.camera ).then( function ( camera ) { return parser._getnoderef( parser.cameracache, nodedef.camera, camera ); } ) ); } parser._invokeall( function ( ext ) { return ext.createnodeattachment && ext.createnodeattachment( nodeindex ); } ).foreach( function ( promise ) { pending.push( promise ); } ); return promise.all( pending ); }() ).then( function ( objects ) { var node; // .isbone isn't in gltf spec. see ._markdefs if ( nodedef.isbone === true ) { node = new three.bone(); } else if ( objects.length > 1 ) { node = new three.group(); } else if ( objects.length === 1 ) { node = objects[ 0 ]; } else { node = new three.object3d(); } if ( node !== objects[ 0 ] ) { for ( var i = 0, il = objects.length; i < il; i ++ ) { node.add( objects[ i ] ); } } if ( nodedef.name ) { node.userdata.name = nodedef.name; node.name = nodename; } assignextrastouserdata( node, nodedef ); if ( nodedef.extensions ) addunknownextensionstouserdata( extensions, node, nodedef ); if ( nodedef.matrix !== undefined ) { var matrix = new three.matrix4(); matrix.fromarray( nodedef.matrix ); node.applymatrix4( matrix ); } else { if ( nodedef.translation !== undefined ) { node.position.fromarray( nodedef.translation ); } if ( nodedef.rotation !== undefined ) { node.quaternion.fromarray( nodedef.rotation ); } if ( nodedef.scale !== undefined ) { node.scale.fromarray( nodedef.scale ); } } parser.associations.set( node, { type: 'nodes', index: nodeindex } ); return node; } ); }; /** * specification: https://github.com/khronosgroup/gltf/tree/master/specification/2.0#scenes * @param {number} sceneindex * @return {promise} */ gltfparser.prototype.loadscene = function () { // scene node hierachy builder function buildnodehierachy( nodeid, parentobject, json, parser ) { var nodedef = json.nodes[ nodeid ]; return parser.getdependency( 'node', nodeid ).then( function ( node ) { if ( nodedef.skin === undefined ) return node; // build skeleton here as well var skinentry; return parser.getdependency( 'skin', nodedef.skin ).then( function ( skin ) { skinentry = skin; var pendingjoints = []; for ( var i = 0, il = skinentry.joints.length; i < il; i ++ ) { pendingjoints.push( parser.getdependency( 'node', skinentry.joints[ i ] ) ); } return promise.all( pendingjoints ); } ).then( function ( jointnodes ) { node.traverse( function ( mesh ) { if ( ! mesh.ismesh ) return; var bones = []; var boneinverses = []; for ( var j = 0, jl = jointnodes.length; j < jl; j ++ ) { var jointnode = jointnodes[ j ]; if ( jointnode ) { bones.push( jointnode ); var mat = new three.matrix4(); if ( skinentry.inversebindmatrices !== undefined ) { mat.fromarray( skinentry.inversebindmatrices.array, j * 16 ); } boneinverses.push( mat ); } else { console.warn( 'three.gltfloader: joint "%s" could not be found.', skinentry.joints[ j ] ); } } mesh.bind( new three.skeleton( bones, boneinverses ), mesh.matrixworld ); } ); return node; } ); } ).then( function ( node ) { // build node hierachy parentobject.add( node ); var pending = []; if ( nodedef.children ) { var children = nodedef.children; for ( var i = 0, il = children.length; i < il; i ++ ) { var child = children[ i ]; pending.push( buildnodehierachy( child, node, json, parser ) ); } } return promise.all( pending ); } ); } return function loadscene( sceneindex ) { var json = this.json; var extensions = this.extensions; var scenedef = this.json.scenes[ sceneindex ]; var parser = this; // loader returns group, not scene. // see: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 var scene = new three.group(); if ( scenedef.name ) scene.name = parser.createuniquename( scenedef.name ); assignextrastouserdata( scene, scenedef ); if ( scenedef.extensions ) addunknownextensionstouserdata( extensions, scene, scenedef ); var nodeids = scenedef.nodes || []; var pending = []; for ( var i = 0, il = nodeids.length; i < il; i ++ ) { pending.push( buildnodehierachy( nodeids[ i ], scene, json, parser ) ); } return promise.all( pending ).then( function () { return scene; } ); }; }(); return gltfloader; } )();