diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index 0a0e3fe7a9b7a5..02c224f7777739 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -1,10 +1,10 @@ ( function () { - var GLTFLoader = function () { + class GLTFLoader extends THREE.Loader { - function GLTFLoader( manager ) { + constructor( manager ) { - THREE.Loader.call( this, manager ); + super( manager ); this.dracoLoader = null; this.ktx2Loader = null; this.meshoptDecoder = null; @@ -42,292 +42,301 @@ } - GLTFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { - constructor: GLTFLoader, - load: function ( url, onLoad, onProgress, onError ) { + load( url, onLoad, onProgress, onError ) { - var scope = this; - var resourcePath; + const scope = this; + let resourcePath; - if ( this.resourcePath !== '' ) { + if ( this.resourcePath !== '' ) { - resourcePath = this.resourcePath; + resourcePath = this.resourcePath; - } else if ( this.path !== '' ) { + } else if ( this.path !== '' ) { - resourcePath = this.path; + resourcePath = this.path; - } else { + } else { - resourcePath = THREE.LoaderUtils.extractUrlBase( url ); + 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. + } // 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 ); + this.manager.itemStart( url ); - var _onError = function ( e ) { + const _onError = function ( e ) { - if ( onError ) { + if ( onError ) { - onError( e ); + onError( e ); - } else { + } else { - console.error( e ); + console.error( e ); - } + } - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); + 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 ) { + const 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 { + try { - scope.parse( data, resourcePath, function ( gltf ) { + scope.parse( data, resourcePath, function ( gltf ) { - onLoad( gltf ); - scope.manager.itemEnd( url ); + onLoad( gltf ); + scope.manager.itemEnd( url ); - }, _onError ); + }, _onError ); - } catch ( e ) { + } catch ( e ) { - _onError( e ); + _onError( e ); - } + } - }, onProgress, _onError ); + }, onProgress, _onError ); - }, - setDRACOLoader: function ( dracoLoader ) { + } - this.dracoLoader = dracoLoader; - return this; + setDRACOLoader( dracoLoader ) { - }, - setDDSLoader: function () { + this.dracoLoader = dracoLoader; + return this; - throw new Error( 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' ); + } - }, - setKTX2Loader: function ( ktx2Loader ) { + setDDSLoader() { - this.ktx2Loader = ktx2Loader; - return this; + throw new Error( 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' ); - }, - setMeshoptDecoder: function ( meshoptDecoder ) { + } - this.meshoptDecoder = meshoptDecoder; - return this; + setKTX2Loader( ktx2Loader ) { - }, - register: function ( callback ) { + this.ktx2Loader = ktx2Loader; + return this; - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + } - this.pluginCallbacks.push( callback ); + setMeshoptDecoder( meshoptDecoder ) { - } + this.meshoptDecoder = meshoptDecoder; + return this; - return this; + } - }, - unregister: function ( callback ) { + register( callback ) { - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + this.pluginCallbacks.push( callback ); - } + } - return this; + return this; - }, - parse: function ( data, path, onLoad, onError ) { + } - var content; - var extensions = {}; - var plugins = {}; + unregister( callback ) { - if ( typeof data === 'string' ) { + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - content = data; + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - } else { + } + + return this; - var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); + } - if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { + parse( data, path, onLoad, onError ) { - try { + let content; + const extensions = {}; + const plugins = {}; - extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); + if ( typeof data === 'string' ) { - } catch ( error ) { + content = data; - if ( onError ) onError( error ); - return; + } else { - } + const magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); - content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { - } else { + try { + + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); - content = THREE.LoaderUtils.decodeText( new Uint8Array( 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 ) { + const json = JSON.parse( content ); - if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); - return; + 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, - requestHeader: this.requestHeader, - manager: this.manager, - ktx2Loader: this.ktx2Loader, - meshoptDecoder: this.meshoptDecoder - } ); - parser.fileLoader.setRequestHeader( this.requestHeader ); + } - for ( var i = 0; i < this.pluginCallbacks.length; i ++ ) { + const parser = new GLTFParser( json, { + path: path || this.resourcePath || '', + crossOrigin: this.crossOrigin, + requestHeader: this.requestHeader, + manager: this.manager, + ktx2Loader: this.ktx2Loader, + meshoptDecoder: this.meshoptDecoder + } ); + parser.fileLoader.setRequestHeader( this.requestHeader ); - 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 + for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { - extensions[ plugin.name ] = true; + const 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 ) { + if ( json.extensionsUsed ) { - var extensionName = json.extensionsUsed[ i ]; - var extensionsRequired = json.extensionsRequired || []; + for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { - switch ( extensionName ) { + const extensionName = json.extensionsUsed[ i ]; + const extensionsRequired = json.extensionsRequired || []; - case EXTENSIONS.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); - break; + switch ( extensionName ) { - case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: - extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); - break; + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); + break; - case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); - break; + case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: + extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); + break; - case EXTENSIONS.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension(); - break; + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); + break; - case EXTENSIONS.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); - break; + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ extensionName ] = new GLTFTextureTransformExtension(); + break; - default: - if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) { + case EXTENSIONS.KHR_MESH_QUANTIZATION: + extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); + break; - console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); + 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 ) { + parser.setExtensions( extensions ); + parser.setPlugins( plugins ); + parser.parse( onLoad, onError ); - return objects[ key ]; + } - }, - add: function ( key, object ) { + } + /* GLTFREGISTRY */ - objects[ key ] = object; - }, - remove: function ( key ) { + function GLTFRegistry() { - delete objects[ key ]; + let objects = {}; + return { + get: function ( key ) { - }, - removeAll: function () { + return objects[ key ]; - objects = {}; + }, + add: function ( key, object ) { - } - }; + objects[ key ] = object; - } - /*********************************/ + }, + remove: function ( key ) { - /********** EXTENSIONS ***********/ + delete objects[ key ]; - /*********************************/ + }, + removeAll: function () { + objects = {}; - 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' + } }; - /** + + } + /*********************************/ + + /********** EXTENSIONS ***********/ + + /*********************************/ + + + const EXTENSIONS = { + KHR_BINARY_GLTF: 'KHR_binary_glTF', + KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', + KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', + KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', + KHR_MATERIALS_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' + }; + /** * Punctual Lights Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ - function GLTFLightsExtension( parser ) { + class GLTFLightsExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; // THREE.Object3D instance caches @@ -339,14 +348,14 @@ } - GLTFLightsExtension.prototype._markDefs = function () { + _markDefs() { - var parser = this.parser; - var nodeDefs = this.parser.json.nodes || []; + const parser = this.parser; + const nodeDefs = this.parser.json.nodes || []; - for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - var nodeDef = nodeDefs[ nodeIndex ]; + const nodeDef = nodeDefs[ nodeIndex ]; if ( nodeDef.extensions && nodeDef.extensions[ this.name ] && nodeDef.extensions[ this.name ].light !== undefined ) { @@ -356,22 +365,22 @@ } - }; + } - GLTFLightsExtension.prototype._loadLight = function ( lightIndex ) { + _loadLight( lightIndex ) { - var parser = this.parser; - var cacheKey = 'light:' + lightIndex; - var dependency = parser.cache.get( cacheKey ); + const parser = this.parser; + const cacheKey = 'light:' + lightIndex; + let 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 ); + const json = parser.json; + const extensions = json.extensions && json.extensions[ this.name ] || {}; + const lightDefs = extensions.lights || []; + const lightDef = lightDefs[ lightIndex ]; + let lightNode; + const color = new THREE.Color( 0xffffff ); if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); - var range = lightDef.range !== undefined ? lightDef.range : 0; + const range = lightDef.range !== undefined ? lightDef.range : 0; switch ( lightDef.type ) { @@ -414,16 +423,16 @@ parser.cache.add( cacheKey, dependency ); return dependency; - }; + } - GLTFLightsExtension.prototype.createNodeAttachment = function ( nodeIndex ) { + createNodeAttachment( 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; + const self = this; + const parser = this.parser; + const json = parser.json; + const nodeDef = json.nodes[ nodeIndex ]; + const lightDef = nodeDef.extensions && nodeDef.extensions[ this.name ] || {}; + const lightIndex = lightDef.light; if ( lightIndex === undefined ) return null; return this._loadLight( lightIndex ).then( function ( light ) { @@ -431,38 +440,42 @@ } ); - }; - /** + } + + } + /** * Unlit Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit */ - function GLTFMaterialsUnlitExtension() { + class GLTFMaterialsUnlitExtension { + + constructor() { this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; } - GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () { + getMaterialType() { return THREE.MeshBasicMaterial; - }; + } - GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) { + extendParams( materialParams, materialDef, parser ) { - var pending = []; + const pending = []; materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); materialParams.opacity = 1.0; - var metallicRoughness = materialDef.pbrMetallicRoughness; + const metallicRoughness = materialDef.pbrMetallicRoughness; if ( metallicRoughness ) { if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - var array = metallicRoughness.baseColorFactor; + const array = metallicRoughness.baseColorFactor; materialParams.color.fromArray( array ); materialParams.opacity = array[ 3 ]; @@ -478,34 +491,38 @@ 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 ) { + class GLTFMaterialsClearcoatExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; } - GLTFMaterialsClearcoatExtension.prototype.getMaterialType = function ( materialIndex ) { + getMaterialType( materialIndex ) { - var parser = this.parser; - var materialDef = parser.json.materials[ materialIndex ]; + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; return THREE.MeshPhysicalMaterial; - }; + } - GLTFMaterialsClearcoatExtension.prototype.extendMaterialParams = function ( materialIndex, materialParams ) { + extendMaterialParams( materialIndex, materialParams ) { - var parser = this.parser; - var materialDef = parser.json.materials[ materialIndex ]; + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { @@ -513,8 +530,8 @@ } - var pending = []; - var extension = materialDef.extensions[ this.name ]; + const pending = []; + const extension = materialDef.extensions[ this.name ]; if ( extension.clearcoatFactor !== undefined ) { @@ -546,7 +563,7 @@ if ( extension.clearcoatNormalTexture.scale !== undefined ) { - var scale = extension.clearcoatNormalTexture.scale; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + const scale = extension.clearcoatNormalTexture.scale; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 materialParams.clearcoatNormalScale = new THREE.Vector2( scale, - scale ); @@ -556,8 +573,10 @@ return Promise.all( pending ); - }; - /** + } + + } + /** * Transmission Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission @@ -565,26 +584,28 @@ */ - function GLTFMaterialsTransmissionExtension( parser ) { + class GLTFMaterialsTransmissionExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; } - GLTFMaterialsTransmissionExtension.prototype.getMaterialType = function ( materialIndex ) { + getMaterialType( materialIndex ) { - var parser = this.parser; - var materialDef = parser.json.materials[ materialIndex ]; + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; return THREE.MeshPhysicalMaterial; - }; + } - GLTFMaterialsTransmissionExtension.prototype.extendMaterialParams = function ( materialIndex, materialParams ) { + extendMaterialParams( materialIndex, materialParams ) { - var parser = this.parser; - var materialDef = parser.json.materials[ materialIndex ]; + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { @@ -592,8 +613,8 @@ } - var pending = []; - var extension = materialDef.extensions[ this.name ]; + const pending = []; + const extension = materialDef.extensions[ this.name ]; if ( extension.transmissionFactor !== undefined ) { @@ -609,26 +630,30 @@ 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 ) { + class GLTFTextureBasisUExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.KHR_TEXTURE_BASISU; } - GLTFTextureBasisUExtension.prototype.loadTexture = function ( textureIndex ) { + loadTexture( textureIndex ) { - var parser = this.parser; - var json = parser.json; - var textureDef = json.textures[ textureIndex ]; + const parser = this.parser; + const json = parser.json; + const textureDef = json.textures[ textureIndex ]; if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { @@ -636,9 +661,9 @@ } - var extension = textureDef.extensions[ this.name ]; - var source = json.images[ extension.source ]; - var loader = parser.options.ktx2Loader; + const extension = textureDef.extensions[ this.name ]; + const source = json.images[ extension.source ]; + const loader = parser.options.ktx2Loader; if ( ! loader ) { @@ -657,15 +682,19 @@ 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 ) { + class GLTFTextureWebPExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.EXT_TEXTURE_WEBP; @@ -673,12 +702,12 @@ } - GLTFTextureWebPExtension.prototype.loadTexture = function ( textureIndex ) { + loadTexture( textureIndex ) { - var name = this.name; - var parser = this.parser; - var json = parser.json; - var textureDef = json.textures[ textureIndex ]; + const name = this.name; + const parser = this.parser; + const json = parser.json; + const textureDef = json.textures[ textureIndex ]; if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { @@ -686,13 +715,13 @@ } - var extension = textureDef.extensions[ name ]; - var source = json.images[ extension.source ]; - var loader = parser.textureLoader; + const extension = textureDef.extensions[ name ]; + const source = json.images[ extension.source ]; + let loader = parser.textureLoader; if ( source.uri ) { - var handler = parser.options.manager.getHandler( source.uri ); + const handler = parser.options.manager.getHandler( source.uri ); if ( handler !== null ) loader = handler; } @@ -712,15 +741,15 @@ } ); - }; + } - GLTFTextureWebPExtension.prototype.detectSupport = function () { + detectSupport() { 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 + const image = new Image(); // Lossy test image. Support for lossy images doesn't guarantee support for all // WebP images, unfortunately. image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; @@ -737,31 +766,35 @@ 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 ) { + class GLTFMeshoptCompression { + + constructor( parser ) { this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; this.parser = parser; } - GLTFMeshoptCompression.prototype.loadBufferView = function ( index ) { + loadBufferView( index ) { - var json = this.parser.json; - var bufferView = json.bufferViews[ index ]; + const json = this.parser.json; + const 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; + const extensionDef = bufferView.extensions[ this.name ]; + const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); + const decoder = this.parser.options.meshoptDecoder; if ( ! decoder || ! decoder.supported ) { @@ -780,12 +813,12 @@ 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 ); + const byteOffset = extensionDef.byteOffset || 0; + const byteLength = extensionDef.byteLength || 0; + const count = extensionDef.count; + const stride = extensionDef.byteStride; + const result = new ArrayBuffer( count * stride ); + const source = new Uint8Array( res[ 0 ], byteOffset, byteLength ); decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); return result; @@ -797,23 +830,27 @@ } - }; - /* BINARY EXTENSION */ + } + } + /* 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 ) { + const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; + const BINARY_EXTENSION_HEADER_LENGTH = 12; + const BINARY_EXTENSION_CHUNK_TYPES = { + JSON: 0x4E4F534A, + BIN: 0x004E4942 + }; + + class GLTFBinaryExtension { + + constructor( data ) { this.name = EXTENSIONS.KHR_BINARY_GLTF; this.content = null; this.body = null; - var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); + const 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 ), @@ -830,25 +867,25 @@ } - var chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; - var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); - var chunkIndex = 0; + const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; + const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); + let chunkIndex = 0; while ( chunkIndex < chunkContentsLength ) { - var chunkLength = chunkView.getUint32( chunkIndex, true ); + const chunkLength = chunkView.getUint32( chunkIndex, true ); chunkIndex += 4; - var chunkType = chunkView.getUint32( chunkIndex, true ); + const 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 ); + const 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; + const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; this.body = data.slice( byteOffset, byteOffset + chunkLength ); } // Clients must ignore chunks with unknown types. @@ -865,14 +902,18 @@ } } - /** + + } + /** * DRACO THREE.Mesh Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression */ - function GLTFDracoMeshCompressionExtension( json, dracoLoader ) { + class GLTFDracoMeshCompressionExtension { + + constructor( json, dracoLoader ) { if ( ! dracoLoader ) { @@ -887,31 +928,31 @@ } - GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { + decodePrimitive( 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 = {}; + const json = this.json; + const dracoLoader = this.dracoLoader; + const bufferViewIndex = primitive.extensions[ this.name ].bufferView; + const gltfAttributeMap = primitive.extensions[ this.name ].attributes; + const threeAttributeMap = {}; + const attributeNormalizedMap = {}; + const attributeTypeMap = {}; - for ( var attributeName in gltfAttributeMap ) { + for ( const attributeName in gltfAttributeMap ) { - var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; } - for ( attributeName in primitive.attributes ) { + for ( const attributeName in primitive.attributes ) { - var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); if ( gltfAttributeMap[ attributeName ] !== undefined ) { - var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; + const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; attributeTypeMap[ threeAttributeName ] = componentType; attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; @@ -925,10 +966,10 @@ dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { - for ( var attributeName in geometry.attributes ) { + for ( const attributeName in geometry.attributes ) { - var attribute = geometry.attributes[ attributeName ]; - var normalized = attributeNormalizedMap[ attributeName ]; + const attribute = geometry.attributes[ attributeName ]; + const normalized = attributeNormalizedMap[ attributeName ]; if ( normalized !== undefined ) attribute.normalized = normalized; } @@ -941,21 +982,25 @@ } ); - }; - /** + } + + } + /** * Texture Transform Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform */ - function GLTFTextureTransformExtension() { + class GLTFTextureTransformExtension { + + constructor() { this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; } - GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) { + extendTexture( texture, transform ) { texture = texture.clone(); @@ -986,31 +1031,35 @@ 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 StandardMaterial with some of the functionality * changed via the `onBeforeCompile` callback * @pailhead */ - function GLTFMeshStandardSGMaterial( params ) { + class GLTFMeshStandardSGMaterial extends THREE.MeshStandardMaterial { - THREE.MeshStandardMaterial.call( this ); + constructor( params ) { + + super(); 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 = { + const specularMapParsFragmentChunk = [ '#ifdef USE_SPECULARMAP', ' uniform sampler2D specularMap;', '#endif' ].join( '\n' ); + const glossinessMapParsFragmentChunk = [ '#ifdef USE_GLOSSINESSMAP', ' uniform sampler2D glossinessMap;', '#endif' ].join( '\n' ); + const 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' ); + const 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' ); + const 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' ); + const uniforms = { specular: { value: new THREE.Color().setHex( 0xffffff ) }, @@ -1028,7 +1077,7 @@ this.onBeforeCompile = function ( shader ) { - for ( var uniformName in uniforms ) { + for ( const uniformName in uniforms ) { shader.uniforms[ uniformName ] = uniforms[ uniformName ]; @@ -1118,12 +1167,9 @@ } - GLTFMeshStandardSGMaterial.prototype = Object.create( THREE.MeshStandardMaterial.prototype ); - GLTFMeshStandardSGMaterial.prototype.constructor = GLTFMeshStandardSGMaterial; - - GLTFMeshStandardSGMaterial.prototype.copy = function ( source ) { + copy( source ) { - THREE.MeshStandardMaterial.prototype.copy.call( this, source ); + super.copy( source ); this.specularMap = source.specularMap; this.specular.copy( source.specular ); this.glossinessMap = source.glossinessMap; @@ -1134,135 +1180,145 @@ 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 () { + class GLTFMaterialsPbrSpecularGlossinessExtension { - return GLTFMeshStandardSGMaterial; + constructor() { - }, - extendParams: function ( materialParams, materialDef, parser ) { + this.name = EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS; + this.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' ]; - 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 ) ) { + getMaterialType() { - var array = pbrSpecularGlossiness.diffuseFactor; - materialParams.color.fromArray( array ); - materialParams.opacity = array[ 3 ]; + return GLTFMeshStandardSGMaterial; - } + } - if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + extendParams( materialParams, materialDef, parser ) { - pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); + const pbrSpecularGlossiness = materialDef.extensions[ this.name ]; + materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + const pending = []; - } + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + + const array = pbrSpecularGlossiness.diffuseFactor; + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; - 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 ) ) { + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { - materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); - } + } - if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + 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 ); - var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; - pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); - pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { - } + materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); - 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; + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { - } - }; + const specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; + pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); + pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); + + } + + return Promise.all( pending ); } - /** + + createMaterial( materialParams ) { + + const 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; + + } + + } + /** * THREE.Mesh Quantization Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization */ - function GLTFMeshQuantizationExtension() { + class GLTFMeshQuantizationExtension { + + constructor() { this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; } - /*********************************/ - /********** INTERPOLATION ********/ + } + /*********************************/ - /*********************************/ - // Spline Interpolation - // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation + /********** 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 ); + class GLTFCubicSplineInterpolant extends THREE.Interpolant { - } + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype ); - GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant; + } - GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) { + copySampleValue_( index ) { // Copies a sample value to the result buffer. See description of glTF // CUBICSPLINE values layout in interpolate_() function below. - var result = this.resultBuffer, + const result = this.resultBuffer, values = this.sampleValues, valueSize = this.valueSize, offset = index * valueSize * 3 + valueSize; - for ( var i = 0; i !== valueSize; i ++ ) { + for ( let i = 0; i !== valueSize; i ++ ) { result[ i ] = values[ offset + i ]; @@ -1270,222 +1326,224 @@ 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 + GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; - if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { + GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { - path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); + const result = this.resultBuffer; + const values = this.sampleValues; + const stride = this.valueSize; + const stride2 = stride * 2; + const stride3 = stride * 3; + const td = t1 - t0; + const p = ( t - t0 ) / td; + const pp = p * p; + const ppp = pp * p; + const offset1 = i1 * stride3; + const offset0 = offset1 - stride3; + const s2 = - 2 * ppp + 3 * pp; + const s3 = ppp - pp; + const s0 = 1 - s2; + const s1 = s3 - pp + p; // Layout of keyframe output values for CUBICSPLINE animations: + // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] - } // Absolute URL http://,https://,// + for ( let i = 0; i !== stride; i ++ ) { + const p0 = values[ offset0 + i + stride ]; // splineVertex_k - if ( /^(https?:)?\/\//i.test( url ) ) return url; // Data URI + const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) - if ( /^data:.*,.*$/i.test( url ) ) return url; // Blob URL + const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 - if ( /^blob:.*$/i.test( url ) ) return url; // Relative URL + const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) - return path + url; + result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; } - /** + + return result; + + }; + /*********************************/ + + /********** INTERNALS ************/ + + /*********************************/ + + /* CONSTANTS */ + + + const WEBGL_CONSTANTS = { + FLOAT: 5126, + //FLOAT_MAT2: 35674, + FLOAT_MAT3: 35675, + FLOAT_MAT4: 35676, + FLOAT_VEC2: 35664, + FLOAT_VEC3: 35665, + FLOAT_VEC4: 35666, + LINEAR: 9729, + REPEAT: 10497, + SAMPLER_2D: 35678, + POINTS: 0, + LINES: 1, + LINE_LOOP: 2, + LINE_STRIP: 3, + TRIANGLES: 4, + TRIANGLE_STRIP: 5, + TRIANGLE_FAN: 6, + UNSIGNED_BYTE: 5121, + UNSIGNED_SHORT: 5123 + }; + const WEBGL_COMPONENT_TYPES = { + 5120: Int8Array, + 5121: Uint8Array, + 5122: Int16Array, + 5123: Uint16Array, + 5125: Uint32Array, + 5126: Float32Array + }; + const WEBGL_FILTERS = { + 9728: THREE.NearestFilter, + 9729: THREE.LinearFilter, + 9984: THREE.NearestMipmapNearestFilter, + 9985: THREE.LinearMipmapNearestFilter, + 9986: THREE.NearestMipmapLinearFilter, + 9987: THREE.LinearMipmapLinearFilter + }; + const WEBGL_WRAPPINGS = { + 33071: THREE.ClampToEdgeWrapping, + 33648: THREE.MirroredRepeatWrapping, + 10497: THREE.RepeatWrapping + }; + const WEBGL_TYPE_SIZES = { + 'SCALAR': 1, + 'VEC2': 2, + 'VEC3': 3, + 'VEC4': 4, + 'MAT2': 4, + 'MAT3': 9, + 'MAT4': 16 + }; + const ATTRIBUTES = { + POSITION: 'position', + NORMAL: 'normal', + TANGENT: 'tangent', + TEXCOORD_0: 'uv', + TEXCOORD_1: 'uv2', + COLOR_0: 'color', + WEIGHTS_0: 'skinWeight', + JOINTS_0: 'skinIndex' + }; + const PATH_PROPERTIES = { + scale: 'scale', + translation: 'position', + rotation: 'quaternion', + weights: 'morphTargetInfluences' + }; + const INTERPOLATION = { + CUBICSPLINE: undefined, + // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each + // keyframe track will be initialized with a default interpolation type, then modified. + LINEAR: THREE.InterpolateLinear, + STEP: THREE.InterpolateDiscrete + }; + const 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 - } ); + function createDefaultMaterial( cache ) { - } + if ( cache[ 'DefaultMaterial' ] === undefined ) { - return cache[ 'DefaultMaterial' ]; + cache[ 'DefaultMaterial' ] = new THREE.MeshStandardMaterial( { + color: 0xFFFFFF, + emissive: 0x000000, + metalness: 1, + roughness: 1, + transparent: false, + depthTest: true, + side: THREE.FrontSide + } ); } - function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { + return cache[ 'DefaultMaterial' ]; - // Add unknown glTF extensions to an object's userData. - for ( var name in objectDef.extensions ) { + } - if ( knownExtensions[ name ] === undefined ) { + function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { - object.userData.gltfExtensions = object.userData.gltfExtensions || {}; - object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; + // Add unknown glTF extensions to an object's userData. + for ( const name in objectDef.extensions ) { - } + if ( knownExtensions[ name ] === undefined ) { + + object.userData.gltfExtensions = object.userData.gltfExtensions || {}; + object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; } } - /** + + } + /** * @param {Object3D|Material|BufferGeometry} object * @param {GLTF.definition} gltfDef */ - function assignExtrasToUserData( object, gltfDef ) { + function assignExtrasToUserData( object, gltfDef ) { - if ( gltfDef.extras !== undefined ) { + if ( gltfDef.extras !== undefined ) { - if ( typeof gltfDef.extras === 'object' ) { + if ( typeof gltfDef.extras === 'object' ) { - Object.assign( object.userData, gltfDef.extras ); - - } else { + Object.assign( object.userData, gltfDef.extras ); - console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); + } else { - } + console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); } } - /** + + } + /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets * * @param {BufferGeometry} geometry @@ -1495,168 +1553,170 @@ */ - function addMorphTargets( geometry, targets, parser ) { + function addMorphTargets( geometry, targets, parser ) { - var hasMorphPosition = false; - var hasMorphNormal = false; + let hasMorphPosition = false; + let hasMorphNormal = false; - for ( var i = 0, il = targets.length; i < il; i ++ ) { + for ( let 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; + const 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 ++ ) { + if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry ); + const pendingPositionAccessors = []; + const pendingNormalAccessors = []; - var target = targets[ i ]; + for ( let i = 0, il = targets.length; i < il; i ++ ) { - if ( hasMorphPosition ) { + const target = targets[ i ]; - var pendingAccessor = target.POSITION !== undefined ? parser.getDependency( 'accessor', target.POSITION ) : geometry.attributes.position; - pendingPositionAccessors.push( pendingAccessor ); + if ( hasMorphPosition ) { - } + const 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 ); + if ( hasMorphNormal ) { - } + const 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; + return Promise.all( [ Promise.all( pendingPositionAccessors ), Promise.all( pendingNormalAccessors ) ] ).then( function ( accessors ) { - } ); + const morphPositions = accessors[ 0 ]; + const morphNormals = accessors[ 1 ]; + if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; + if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; + geometry.morphTargetsRelative = true; + return geometry; - } - /** + } ); + + } + /** * @param {Mesh} mesh * @param {GLTF.Mesh} meshDef */ - function updateMorphTargets( mesh, meshDef ) { - - mesh.updateMorphTargets(); + function updateMorphTargets( mesh, meshDef ) { - if ( meshDef.weights !== undefined ) { + mesh.updateMorphTargets(); - for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { + if ( meshDef.weights !== undefined ) { - mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; + for ( let i = 0, il = meshDef.weights.length; i < il; i ++ ) { - } + mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; - } // .extras has user-defined data, so check that .extras.targetNames is an array. + } + } // .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 ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { - if ( mesh.morphTargetInfluences.length === targetNames.length ) { + const targetNames = meshDef.extras.targetNames; - mesh.morphTargetDictionary = {}; + if ( mesh.morphTargetInfluences.length === targetNames.length ) { - for ( var i = 0, il = targetNames.length; i < il; i ++ ) { + mesh.morphTargetDictionary = {}; - mesh.morphTargetDictionary[ targetNames[ i ] ] = i; + for ( let i = 0, il = targetNames.length; i < il; i ++ ) { - } + mesh.morphTargetDictionary[ targetNames[ i ] ] = i; - } else { + } - console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); + } 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 ) { + function createPrimitiveKey( primitiveDef ) { - geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices + ':' + createAttributesKey( dracoExtension.attributes ); + const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; + let geometryKey; - } else { + if ( dracoExtension ) { - geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; + geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices + ':' + createAttributesKey( dracoExtension.attributes ); - } + } else { - return geometryKey; + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; } - function createAttributesKey( attributes ) { + return geometryKey; - var attributesKey = ''; - var keys = Object.keys( attributes ).sort(); + } - for ( var i = 0, il = keys.length; i < il; i ++ ) { + function createAttributesKey( attributes ) { - attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; + let attributesKey = ''; + const keys = Object.keys( attributes ).sort(); - } + for ( let i = 0, il = keys.length; i < il; i ++ ) { - return attributesKey; + attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; } - function getNormalizedComponentScale( constructor ) { + return attributesKey; - // Reference: - // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data - switch ( constructor ) { + } - case Int8Array: - return 1 / 127; + function getNormalizedComponentScale( constructor ) { - case Uint8Array: - return 1 / 255; + // Reference: + // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data + switch ( constructor ) { - case Int16Array: - return 1 / 32767; + case Int8Array: + return 1 / 127; - case Uint16Array: - return 1 / 65535; + case Uint8Array: + return 1 / 255; - default: - throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); + case Int16Array: + return 1 / 32767; - } + case Uint16Array: + return 1 / 65535; + + default: + throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); } - /* GLTF PARSER */ + } + /* GLTF PARSER */ - function GLTFParser( json, options ) { - this.json = json || {}; + class GLTFParser { + + constructor( json = {}, options = {} ) { + + this.json = json; this.extensions = {}; this.plugins = {}; - this.options = options || {}; // loader object cache + this.options = options; // loader object cache this.cache = new GLTFRegistry(); // associations between Three.js objects and glTF elements @@ -1703,23 +1763,23 @@ } - GLTFParser.prototype.setExtensions = function ( extensions ) { + setExtensions( extensions ) { this.extensions = extensions; - }; + } - GLTFParser.prototype.setPlugins = function ( plugins ) { + setPlugins( plugins ) { this.plugins = plugins; - }; + } - GLTFParser.prototype.parse = function ( onLoad, onError ) { + parse( onLoad, onError ) { - var parser = this; - var json = this.json; - var extensions = this.extensions; // Clear the loader cache + const parser = this; + const json = this.json; + const extensions = this.extensions; // Clear the loader cache this.cache.removeAll(); // Mark the special nodes/meshes in json for efficient parse @@ -1739,7 +1799,7 @@ } ).then( function ( dependencies ) { - var result = { + const result = { scene: dependencies[ 0 ][ json.scene || 0 ], scenes: dependencies[ 0 ], animations: dependencies[ 1 ], @@ -1762,24 +1822,24 @@ } ).catch( onError ); - }; + } /** * Marks the special nodes/meshes in json for efficient parse. */ - GLTFParser.prototype._markDefs = function () { + _markDefs() { - 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 THREE.Bone or an + const nodeDefs = this.json.nodes || []; + const skinDefs = this.json.skins || []; + const meshDefs = this.json.meshes || []; // Nothing in the node definition indicates whether it is a THREE.Bone or an // THREE.Object3D. Use the skins' joint references to mark bones. - for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { + for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { - var joints = skinDefs[ skinIndex ].joints; + const joints = skinDefs[ skinIndex ].joints; - for ( var i = 0, il = joints.length; i < il; i ++ ) { + for ( let i = 0, il = joints.length; i < il; i ++ ) { nodeDefs[ joints[ i ] ].isBone = true; @@ -1789,9 +1849,9 @@ // as well as skeleton joints. - for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - var nodeDef = nodeDefs[ nodeIndex ]; + const nodeDef = nodeDefs[ nodeIndex ]; if ( nodeDef.mesh !== undefined ) { @@ -1816,7 +1876,7 @@ } - }; + } /** * Counts references to shared node / THREE.Object3D resources. These resources * can be reused, or "instantiated", at multiple nodes in the scene @@ -1828,7 +1888,7 @@ */ - GLTFParser.prototype._addNodeRef = function ( cache, index ) { + _addNodeRef( cache, index ) { if ( index === undefined ) return; @@ -1840,51 +1900,51 @@ cache.refs[ index ] ++; - }; + } /** Returns a reference to a shared resource, cloning it if necessary. */ - GLTFParser.prototype._getNodeRef = function ( cache, index, object ) { + _getNodeRef( cache, index, object ) { if ( cache.refs[ index ] <= 1 ) return object; - var ref = object.clone(); + const ref = object.clone(); ref.name += '_instance_' + cache.uses[ index ] ++; return ref; - }; + } - GLTFParser.prototype._invokeOne = function ( func ) { + _invokeOne( func ) { - var extensions = Object.values( this.plugins ); + const extensions = Object.values( this.plugins ); extensions.push( this ); - for ( var i = 0; i < extensions.length; i ++ ) { + for ( let i = 0; i < extensions.length; i ++ ) { - var result = func( extensions[ i ] ); + const result = func( extensions[ i ] ); if ( result ) return result; } return null; - }; + } - GLTFParser.prototype._invokeAll = function ( func ) { + _invokeAll( func ) { - var extensions = Object.values( this.plugins ); + const extensions = Object.values( this.plugins ); extensions.unshift( this ); - var pending = []; + const pending = []; - for ( var i = 0; i < extensions.length; i ++ ) { + for ( let i = 0; i < extensions.length; i ++ ) { - var result = func( extensions[ i ] ); + const result = func( extensions[ i ] ); if ( result ) pending.push( result ); } return pending; - }; + } /** * Requests the specified dependency asynchronously, with caching. * @param {string} type @@ -1893,10 +1953,10 @@ */ - GLTFParser.prototype.getDependency = function ( type, index ) { + getDependency( type, index ) { - var cacheKey = type + ':' + index; - var dependency = this.cache.get( cacheKey ); + const cacheKey = type + ':' + index; + let dependency = this.cache.get( cacheKey ); if ( ! dependency ) { @@ -1973,7 +2033,7 @@ return dependency; - }; + } /** * Requests all dependencies of the specified type asynchronously, with caching. * @param {string} type @@ -1981,14 +2041,14 @@ */ - GLTFParser.prototype.getDependencies = function ( type ) { + getDependencies( type ) { - var dependencies = this.cache.get( type ); + let dependencies = this.cache.get( type ); if ( ! dependencies ) { - var parser = this; - var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; + const parser = this; + const defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; dependencies = Promise.all( defs.map( function ( def, index ) { return parser.getDependency( type, index ); @@ -2000,7 +2060,7 @@ return dependencies; - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views * @param {number} bufferIndex @@ -2008,10 +2068,10 @@ */ - GLTFParser.prototype.loadBuffer = function ( bufferIndex ) { + loadBuffer( bufferIndex ) { - var bufferDef = this.json.buffers[ bufferIndex ]; - var loader = this.fileLoader; + const bufferDef = this.json.buffers[ bufferIndex ]; + const loader = this.fileLoader; if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { @@ -2026,7 +2086,7 @@ } - var options = this.options; + const options = this.options; return new Promise( function ( resolve, reject ) { loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { @@ -2037,7 +2097,7 @@ } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views * @param {number} bufferViewIndex @@ -2045,18 +2105,18 @@ */ - GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) { + loadBufferView( bufferViewIndex ) { - var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; + const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { - var byteLength = bufferViewDef.byteLength || 0; - var byteOffset = bufferViewDef.byteOffset || 0; + const byteLength = bufferViewDef.byteLength || 0; + const byteOffset = bufferViewDef.byteOffset || 0; return buffer.slice( byteOffset, byteOffset + byteLength ); } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors * @param {number} accessorIndex @@ -2064,11 +2124,11 @@ */ - GLTFParser.prototype.loadAccessor = function ( accessorIndex ) { + loadAccessor( accessorIndex ) { - var parser = this; - var json = this.json; - var accessorDef = this.json.accessors[ accessorIndex ]; + const parser = this; + const json = this.json; + const accessorDef = this.json.accessors[ accessorIndex ]; if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { @@ -2079,7 +2139,7 @@ } - var pendingBufferViews = []; + const pendingBufferViews = []; if ( accessorDef.bufferView !== undefined ) { @@ -2100,24 +2160,24 @@ 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. + const bufferView = bufferViews[ 0 ]; + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. - 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. + const elementBytes = TypedArray.BYTES_PER_ELEMENT; + const itemBytes = elementBytes * itemSize; + const byteOffset = accessorDef.byteOffset || 0; + const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; + const normalized = accessorDef.normalized === true; + let array, bufferAttribute; // The buffer is not interleaved if the stride is the item size in bytes. if ( byteStride && byteStride !== itemBytes ) { // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own THREE.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 ); + const ibSlice = Math.floor( byteOffset / byteStride ); + const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; + let ib = parser.cache.get( ibCacheKey ); if ( ! ib ) { @@ -2149,12 +2209,12 @@ 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 ); + const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; + const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; + const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; + const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); + const sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); if ( bufferView !== null ) { @@ -2163,9 +2223,9 @@ } - for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) { + for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { - var index = sparseIndices[ i ]; + const index = sparseIndices[ i ]; bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); @@ -2180,7 +2240,7 @@ } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures * @param {number} textureIndex @@ -2188,35 +2248,35 @@ */ - GLTFParser.prototype.loadTexture = function ( textureIndex ) { + loadTexture( textureIndex ) { - var json = this.json; - var options = this.options; - var textureDef = json.textures[ textureIndex ]; - var source = json.images[ textureDef.source ]; - var loader = this.textureLoader; + const json = this.json; + const options = this.options; + const textureDef = json.textures[ textureIndex ]; + const source = json.images[ textureDef.source ]; + let loader = this.textureLoader; if ( source.uri ) { - var handler = options.manager.getHandler( source.uri ); + const handler = options.manager.getHandler( source.uri ); if ( handler !== null ) loader = handler; } return this.loadTextureImage( textureIndex, source, loader ); - }; + } - GLTFParser.prototype.loadTextureImage = function ( textureIndex, source, loader ) { + loadTextureImage( 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; + const parser = this; + const json = this.json; + const options = this.options; + const textureDef = json.textures[ textureIndex ]; + const URL = self.URL || self.webkitURL; + let sourceURI = source.uri; + let isObjectURL = false; + let hasAlpha = true; if ( source.mimeType === 'image/jpeg' ) hasAlpha = false; if ( source.bufferView !== undefined ) { @@ -2232,13 +2292,13 @@ // sometimes contains alpha. // // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header - var colorType = new DataView( bufferView, 25, 1 ).getUint8( 0, false ); + const colorType = new DataView( bufferView, 25, 1 ).getUint8( 0, false ); hasAlpha = colorType === 6 || colorType === 4 || colorType === 3; } isObjectURL = true; - var blob = new Blob( [ bufferView ], { + const blob = new Blob( [ bufferView ], { type: source.mimeType } ); sourceURI = URL.createObjectURL( blob ); @@ -2256,7 +2316,7 @@ return new Promise( function ( resolve, reject ) { - var onLoad = resolve; + let onLoad = resolve; if ( loader.isImageBitmapLoader === true ) { @@ -2285,8 +2345,8 @@ if ( textureDef.name ) texture.name = textureDef.name; // When there is definitely no alpha channel in the texture, set THREE.RGBFormat to save space. if ( ! hasAlpha ) texture.format = THREE.RGBFormat; - var samplers = json.samplers || {}; - var sampler = samplers[ textureDef.sampler ] || {}; + const samplers = json.samplers || {}; + const 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; @@ -2299,7 +2359,7 @@ } ); - }; + } /** * Asynchronously assigns a texture to the given material parameters. * @param {Object} materialParams @@ -2309,9 +2369,9 @@ */ - GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) { + assignTexture( materialParams, mapName, mapDef ) { - var parser = this; + const 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 @@ -2324,11 +2384,11 @@ if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; + const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; if ( transform ) { - var gltfReference = parser.associations.get( texture ); + const gltfReference = parser.associations.get( texture ); texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); parser.associations.set( texture, gltfReference ); @@ -2340,7 +2400,7 @@ } ); - }; + } /** * Assigns final material to a THREE.Mesh, THREE.Line, or THREE.Points instance. The instance * already has a material (generated from the glTF material options alone) @@ -2351,21 +2411,21 @@ */ - GLTFParser.prototype.assignFinalMaterial = function ( mesh ) { + assignFinalMaterial( 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; + const geometry = mesh.geometry; + let material = mesh.material; + const useVertexTangents = geometry.attributes.tangent !== undefined; + const useVertexColors = geometry.attributes.color !== undefined; + const useFlatShading = geometry.attributes.normal === undefined; + const useSkinning = mesh.isSkinnedMesh === true; + const useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0; + const useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined; if ( mesh.isPoints ) { - var cacheKey = 'PointsMaterial:' + material.uuid; - var pointsMaterial = this.cache.get( cacheKey ); + const cacheKey = 'PointsMaterial:' + material.uuid; + let pointsMaterial = this.cache.get( cacheKey ); if ( ! pointsMaterial ) { @@ -2383,8 +2443,8 @@ } else if ( mesh.isLine ) { - var cacheKey = 'LineBasicMaterial:' + material.uuid; - var lineMaterial = this.cache.get( cacheKey ); + const cacheKey = 'LineBasicMaterial:' + material.uuid; + let lineMaterial = this.cache.get( cacheKey ); if ( ! lineMaterial ) { @@ -2402,7 +2462,7 @@ if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { - var cacheKey = 'ClonedMaterial:' + material.uuid + ':'; + let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; if ( useSkinning ) cacheKey += 'skinning:'; if ( useVertexTangents ) cacheKey += 'vertex-tangents:'; @@ -2410,7 +2470,7 @@ if ( useFlatShading ) cacheKey += 'flat-shading:'; if ( useMorphTargets ) cacheKey += 'morph-targets:'; if ( useMorphNormals ) cacheKey += 'morph-normals:'; - var cachedMaterial = this.cache.get( cacheKey ); + let cachedMaterial = this.cache.get( cacheKey ); if ( ! cachedMaterial ) { @@ -2448,13 +2508,13 @@ mesh.material = material; - }; + } - GLTFParser.prototype.getMaterialType = function ( ) { + getMaterialType( ) { return THREE.MeshStandardMaterial; - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials * @param {number} materialIndex @@ -2462,26 +2522,26 @@ */ - GLTFParser.prototype.loadMaterial = function ( materialIndex ) { + loadMaterial( 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 = []; + const parser = this; + const json = this.json; + const extensions = this.extensions; + const materialDef = json.materials[ materialIndex ]; + let materialType; + const materialParams = {}; + const materialExtensions = materialDef.extensions || {}; + const pending = []; if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { - var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; + const 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 ]; + const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; materialType = kmuExtension.getMaterialType(); pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); @@ -2489,13 +2549,13 @@ // Specification: // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material - var metallicRoughness = materialDef.pbrMetallicRoughness || {}; + const 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; + const array = metallicRoughness.baseColorFactor; materialParams.color.fromArray( array ); materialParams.opacity = array[ 3 ]; @@ -2536,7 +2596,7 @@ } - var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; + const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; if ( alphaMode === ALPHA_MODES.BLEND ) { @@ -2596,7 +2656,7 @@ return Promise.all( pending ).then( function () { - var material; + let material; if ( materialType === GLTFMeshStandardSGMaterial ) { @@ -2622,16 +2682,16 @@ } ); - }; + } /** When THREE.Object3D instances are targeted by animation, they need unique names. */ - GLTFParser.prototype.createUniqueName = function ( originalName ) { + createUniqueName( originalName ) { - var sanitizedName = THREE.PropertyBinding.sanitizeNodeName( originalName || '' ); - var name = sanitizedName; + const sanitizedName = THREE.PropertyBinding.sanitizeNodeName( originalName || '' ); + let name = sanitizedName; - for ( var i = 1; this.nodeNamesUsed[ name ]; ++ i ) { + for ( let i = 1; this.nodeNamesUsed[ name ]; ++ i ) { name = sanitizedName + '_' + i; @@ -2640,246 +2700,6 @@ this.nodeNamesUsed[ name ] = true; return name; - }; - /** - * @param {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 ] ) ); - - if ( accessor.normalized ) { - - var boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - box.min.multiplyScalar( boxScale ); - box.max.multiplyScalar( boxScale ); - - } - - } else { - - console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); - return; - - } - - } else { - - return; - - } - - 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 ] ) ) ); - - if ( accessor.normalized ) { - - var boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - vector.multiplyScalar( boxScale ); - - } // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative - // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets - // are used to implement key-frame animations and as such only two are active at a time - this results in very large - // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. - - - maxDisplacement.max( vector ); - - } else { - - console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); - - } - - } - - } // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. - - - box.expandByVector( maxDisplacement ); - - } - - geometry.boundingBox = box; - var sphere = new THREE.Sphere(); - box.getCenter( sphere.center ); - sphere.radius = box.min.distanceTo( box.max ) / 2; - geometry.boundingSphere = sphere; - - } - /** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - * @return {Promise} - */ - - - 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 {BufferGeometry} geometry - * @param {Number} drawMode - * @return {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 @@ -2891,11 +2711,11 @@ */ - GLTFParser.prototype.loadGeometries = function ( primitives ) { + loadGeometries( primitives ) { - var parser = this; - var extensions = this.extensions; - var cache = this.primitiveCache; + const parser = this; + const extensions = this.extensions; + const cache = this.primitiveCache; function createDracoPrimitive( primitive ) { @@ -2907,14 +2727,14 @@ } - var pending = []; + const pending = []; - for ( var i = 0, il = primitives.length; i < il; i ++ ) { + for ( let i = 0, il = primitives.length; i < il; i ++ ) { - var primitive = primitives[ i ]; - var cacheKey = createPrimitiveKey( primitive ); // See if we've already created this geometry + const primitive = primitives[ i ]; + const cacheKey = createPrimitiveKey( primitive ); // See if we've already created this geometry - var cached = cache[ cacheKey ]; + const cached = cache[ cacheKey ]; if ( cached ) { @@ -2923,7 +2743,7 @@ } else { - var geometryPromise; + let geometryPromise; if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { @@ -2950,7 +2770,7 @@ return Promise.all( pending ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes * @param {number} meshIndex @@ -2958,18 +2778,18 @@ */ - GLTFParser.prototype.loadMesh = function ( meshIndex ) { + loadMesh( meshIndex ) { - var parser = this; - var json = this.json; - var extensions = this.extensions; - var meshDef = json.meshes[ meshIndex ]; - var primitives = meshDef.primitives; - var pending = []; + const parser = this; + const json = this.json; + const extensions = this.extensions; + const meshDef = json.meshes[ meshIndex ]; + const primitives = meshDef.primitives; + const pending = []; - for ( var i = 0, il = primitives.length; i < il; i ++ ) { + for ( let i = 0, il = primitives.length; i < il; i ++ ) { - var material = primitives[ i ].material === undefined ? createDefaultMaterial( this.cache ) : this.getDependency( 'material', primitives[ i ].material ); + const material = primitives[ i ].material === undefined ? createDefaultMaterial( this.cache ) : this.getDependency( 'material', primitives[ i ].material ); pending.push( material ); } @@ -2977,17 +2797,17 @@ 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 = []; + const materials = results.slice( 0, results.length - 1 ); + const geometries = results[ results.length - 1 ]; + const meshes = []; - for ( var i = 0, il = geometries.length; i < il; i ++ ) { + for ( let i = 0, il = geometries.length; i < il; i ++ ) { - var geometry = geometries[ i ]; - var primitive = primitives[ i ]; // 1. create THREE.Mesh + const geometry = geometries[ i ]; + const primitive = primitives[ i ]; // 1. create THREE.Mesh - var mesh; - var material = materials[ i ]; + let mesh; + const material = materials[ i ]; if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || primitive.mode === undefined ) { @@ -3054,9 +2874,9 @@ } - var group = new THREE.Group(); + const group = new THREE.Group(); - for ( var i = 0, il = meshes.length; i < il; i ++ ) { + for ( let i = 0, il = meshes.length; i < il; i ++ ) { group.add( meshes[ i ] ); @@ -3066,7 +2886,7 @@ } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras * @param {number} cameraIndex @@ -3074,11 +2894,11 @@ */ - GLTFParser.prototype.loadCamera = function ( cameraIndex ) { + loadCamera( cameraIndex ) { - var camera; - var cameraDef = this.json.cameras[ cameraIndex ]; - var params = cameraDef[ cameraDef.type ]; + let camera; + const cameraDef = this.json.cameras[ cameraIndex ]; + const params = cameraDef[ cameraDef.type ]; if ( ! params ) { @@ -3101,7 +2921,7 @@ assignExtrasToUserData( camera, cameraDef ); return Promise.resolve( camera ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins * @param {number} skinIndex @@ -3109,10 +2929,10 @@ */ - GLTFParser.prototype.loadSkin = function ( skinIndex ) { + loadSkin( skinIndex ) { - var skinDef = this.json.skins[ skinIndex ]; - var skinEntry = { + const skinDef = this.json.skins[ skinIndex ]; + const skinEntry = { joints: skinDef.joints }; @@ -3129,7 +2949,7 @@ } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations * @param {number} animationIndex @@ -3137,25 +2957,25 @@ */ - GLTFParser.prototype.loadAnimation = function ( animationIndex ) { + loadAnimation( animationIndex ) { - var json = this.json; - var animationDef = json.animations[ animationIndex ]; - var pendingNodes = []; - var pendingInputAccessors = []; - var pendingOutputAccessors = []; - var pendingSamplers = []; - var pendingTargets = []; + const json = this.json; + const animationDef = json.animations[ animationIndex ]; + const pendingNodes = []; + const pendingInputAccessors = []; + const pendingOutputAccessors = []; + const pendingSamplers = []; + const pendingTargets = []; - for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) { + for ( let 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. + const channel = animationDef.channels[ i ]; + const sampler = animationDef.samplers[ channel.sampler ]; + const target = channel.target; + const 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; + const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; + const 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 ) ); @@ -3166,24 +2986,24 @@ 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 = []; + const nodes = dependencies[ 0 ]; + const inputAccessors = dependencies[ 1 ]; + const outputAccessors = dependencies[ 2 ]; + const samplers = dependencies[ 3 ]; + const targets = dependencies[ 4 ]; + const tracks = []; - for ( var i = 0, il = nodes.length; i < il; i ++ ) { + for ( let 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 ]; + const node = nodes[ i ]; + const inputAccessor = inputAccessors[ i ]; + const outputAccessor = outputAccessors[ i ]; + const sampler = samplers[ i ]; + const target = targets[ i ]; if ( node === undefined ) continue; node.updateMatrix(); node.matrixAutoUpdate = true; - var TypedKeyframeTrack; + let TypedKeyframeTrack; switch ( PATH_PROPERTIES[ target.path ] ) { @@ -3203,9 +3023,9 @@ } - var targetName = node.name ? node.name : node.uuid; - var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear; - var targetNames = []; + const targetName = node.name ? node.name : node.uuid; + const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear; + const targetNames = []; if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { @@ -3226,14 +3046,14 @@ } - var outputArray = outputAccessor.array; + let outputArray = outputAccessor.array; if ( outputAccessor.normalized ) { - var scale = getNormalizedComponentScale( outputArray.constructor ); - var scaled = new Float32Array( outputArray.length ); + const scale = getNormalizedComponentScale( outputArray.constructor ); + const scaled = new Float32Array( outputArray.length ); - for ( var j = 0, jl = outputArray.length; j < jl; j ++ ) { + for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { scaled[ j ] = outputArray[ j ] * scale; @@ -3243,9 +3063,9 @@ } - for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) { + for ( let 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. + const track = new TypedKeyframeTrack( targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], inputAccessor.array, outputArray, interpolation ); // Override interpolation with custom factory method. if ( sampler.interpolation === 'CUBICSPLINE' ) { @@ -3269,22 +3089,22 @@ } - var name = animationDef.name ? animationDef.name : 'animation_' + animationIndex; + const name = animationDef.name ? animationDef.name : 'animation_' + animationIndex; return new THREE.AnimationClip( name, undefined, tracks ); } ); - }; + } - GLTFParser.prototype.createNodeMesh = function ( nodeIndex ) { + createNodeMesh( nodeIndex ) { - var json = this.json; - var parser = this; - var nodeDef = json.nodes[ nodeIndex ]; + const json = this.json; + const parser = this; + const nodeDef = json.nodes[ nodeIndex ]; if ( nodeDef.mesh === undefined ) return null; return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { - var node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); // if weights are provided on the node, override weights on the mesh. + const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); // if weights are provided on the node, override weights on the mesh. if ( nodeDef.weights !== undefined ) { @@ -3293,7 +3113,7 @@ if ( ! o.isMesh ) return; - for ( var i = 0, il = nodeDef.weights.length; i < il; i ++ ) { + for ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; @@ -3307,7 +3127,7 @@ } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy * @param {number} nodeIndex @@ -3315,19 +3135,19 @@ */ - GLTFParser.prototype.loadNode = function ( nodeIndex ) { + loadNode( 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. + const json = this.json; + const extensions = this.extensions; + const parser = this; + const 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 ) : ''; + const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; return function () { - var pending = []; + const pending = []; - var meshPromise = parser._invokeOne( function ( ext ) { + const meshPromise = parser._invokeOne( function ( ext ) { return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); @@ -3363,7 +3183,7 @@ }().then( function ( objects ) { - var node; // .isBone isn't in glTF spec. See ._markDefs + let node; // .isBone isn't in glTF spec. See ._markDefs if ( nodeDef.isBone === true ) { @@ -3385,7 +3205,7 @@ if ( node !== objects[ 0 ] ) { - for ( var i = 0, il = objects.length; i < il; i ++ ) { + for ( let i = 0, il = objects.length; i < il; i ++ ) { node.add( objects[ i ] ); @@ -3405,7 +3225,7 @@ if ( nodeDef.matrix !== undefined ) { - var matrix = new THREE.Matrix4(); + const matrix = new THREE.Matrix4(); matrix.fromArray( nodeDef.matrix ); node.applyMatrix4( matrix ); @@ -3439,7 +3259,7 @@ } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes * @param {number} sceneIndex @@ -3447,129 +3267,362 @@ */ - GLTFParser.prototype.loadScene = function () { + loadScene( sceneIndex ) { - // scene node hierachy builder - function buildNodeHierachy( nodeId, parentObject, json, parser ) { + const json = this.json; + const extensions = this.extensions; + const sceneDef = this.json.scenes[ sceneIndex ]; + const parser = this; // THREE.Loader returns THREE.Group, not Scene. + // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 - var nodeDef = json.nodes[ nodeId ]; - return parser.getDependency( 'node', nodeId ).then( function ( node ) { + const scene = new THREE.Group(); + if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); + assignExtrasToUserData( scene, sceneDef ); + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); + const nodeIds = sceneDef.nodes || []; + const pending = []; - if ( nodeDef.skin === undefined ) return node; // build skeleton here as well + for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { - var skinEntry; - return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { + pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); - skinEntry = skin; - var pendingJoints = []; + } - for ( var i = 0, il = skinEntry.joints.length; i < il; i ++ ) { + return Promise.all( pending ).then( function () { - pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); + return scene; - } - - return Promise.all( pendingJoints ); + } ); - } ).then( function ( jointNodes ) { + } - node.traverse( function ( mesh ) { + } - if ( ! mesh.isMesh ) return; - var bones = []; - var boneInverses = []; + function buildNodeHierachy( nodeId, parentObject, json, parser ) { - for ( var j = 0, jl = jointNodes.length; j < jl; j ++ ) { + const nodeDef = json.nodes[ nodeId ]; + return parser.getDependency( 'node', nodeId ).then( function ( node ) { - var jointNode = jointNodes[ j ]; + if ( nodeDef.skin === undefined ) return node; // build skeleton here as well - if ( jointNode ) { + let skinEntry; + return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { - bones.push( jointNode ); - var mat = new THREE.Matrix4(); + skinEntry = skin; + const pendingJoints = []; - if ( skinEntry.inverseBindMatrices !== undefined ) { + for ( let i = 0, il = skinEntry.joints.length; i < il; i ++ ) { - mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); + pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); - } + } - boneInverses.push( mat ); + return Promise.all( pendingJoints ); - } else { + } ).then( function ( jointNodes ) { - console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); + node.traverse( function ( mesh ) { - } + if ( ! mesh.isMesh ) return; + const bones = []; + const boneInverses = []; - } + for ( let j = 0, jl = jointNodes.length; j < jl; j ++ ) { - mesh.bind( new THREE.Skeleton( bones, boneInverses ), mesh.matrixWorld ); + const jointNode = jointNodes[ j ]; - } ); - return node; + if ( jointNode ) { - } ); + bones.push( jointNode ); + const mat = new THREE.Matrix4(); - } ).then( function ( node ) { + if ( skinEntry.inverseBindMatrices !== undefined ) { - // build node hierachy - parentObject.add( node ); - var pending = []; + mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); - if ( nodeDef.children ) { + } - var children = nodeDef.children; + boneInverses.push( mat ); - for ( var i = 0, il = children.length; i < il; i ++ ) { + } else { - var child = children[ i ]; - pending.push( buildNodeHierachy( child, node, json, parser ) ); + console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); } } - return Promise.all( pending ); + mesh.bind( new THREE.Skeleton( bones, boneInverses ), mesh.matrixWorld ); } ); + return node; + + } ); + + } ).then( function ( node ) { + + // build node hierachy + parentObject.add( node ); + const pending = []; + + if ( nodeDef.children ) { + + const children = nodeDef.children; + + for ( let i = 0, il = children.length; i < il; i ++ ) { + + const child = children[ i ]; + pending.push( buildNodeHierachy( child, node, json, parser ) ); + + } } - return function loadScene( sceneIndex ) { + return Promise.all( pending ); + + } ); + + } + /** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + */ + + + function computeBounds( geometry, primitiveDef, parser ) { + + const attributes = primitiveDef.attributes; + const box = new THREE.Box3(); - var json = this.json; - var extensions = this.extensions; - var sceneDef = this.json.scenes[ sceneIndex ]; - var parser = this; // THREE.Loader returns THREE.Group, not Scene. - // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 + if ( attributes.POSITION !== undefined ) { - 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 = []; + const accessor = parser.json.accessors[ attributes.POSITION ]; + const min = accessor.min; + const max = accessor.max; // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - for ( var i = 0, il = nodeIds.length; i < il; i ++ ) { + if ( min !== undefined && max !== undefined ) { - pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); + box.set( new THREE.Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), new THREE.Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) ); + + if ( accessor.normalized ) { + + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); + box.min.multiplyScalar( boxScale ); + box.max.multiplyScalar( boxScale ); } - return Promise.all( pending ).then( function () { + } else { - return scene; + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + return; - } ); + } - }; + } else { + + return; + + } + + const targets = primitiveDef.targets; + + if ( targets !== undefined ) { + + const maxDisplacement = new THREE.Vector3(); + const vector = new THREE.Vector3(); + + for ( let i = 0, il = targets.length; i < il; i ++ ) { + + const target = targets[ i ]; + + if ( target.POSITION !== undefined ) { + + const accessor = parser.json.accessors[ target.POSITION ]; + const min = accessor.min; + const max = accessor.max; // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. + + if ( min !== undefined && max !== undefined ) { + + // we need to get max of absolute components because target weight is [-1,1] + vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); + vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); + vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); + + if ( accessor.normalized ) { + + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); + vector.multiplyScalar( boxScale ); + + } // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative + // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets + // are used to implement key-frame animations and as such only two are active at a time - this results in very large + // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. + + + maxDisplacement.max( vector ); + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + + } + + } + + } // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. + + + box.expandByVector( maxDisplacement ); + + } + + geometry.boundingBox = box; + const sphere = new THREE.Sphere(); + box.getCenter( sphere.center ); + sphere.radius = box.min.distanceTo( box.max ) / 2; + geometry.boundingSphere = sphere; + + } + /** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + * @return {Promise} + */ + + + function addPrimitiveAttributes( geometry, primitiveDef, parser ) { + + const attributes = primitiveDef.attributes; + const pending = []; + + function assignAttributeAccessor( accessorIndex, attributeName ) { + + return parser.getDependency( 'accessor', accessorIndex ).then( function ( accessor ) { + + geometry.setAttribute( attributeName, accessor ); + + } ); + + } + + for ( const gltfAttributeName in attributes ) { + + const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); // Skip attributes already provided by e.g. Draco extension. + + if ( threeAttributeName in geometry.attributes ) continue; + pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); + + } + + if ( primitiveDef.indices !== undefined && ! geometry.index ) { + + const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { + + geometry.setIndex( accessor ); + + } ); + pending.push( accessor ); + + } + + assignExtrasToUserData( geometry, primitiveDef ); + computeBounds( geometry, primitiveDef, parser ); + return Promise.all( pending ).then( function () { + + return primitiveDef.targets !== undefined ? addMorphTargets( geometry, primitiveDef.targets, parser ) : geometry; + + } ); + + } + /** + * @param {BufferGeometry} geometry + * @param {Number} drawMode + * @return {BufferGeometry} + */ + + + function toTrianglesDrawMode( geometry, drawMode ) { + + let index = geometry.getIndex(); // generate index if not present + + if ( index === null ) { + + const indices = []; + const position = geometry.getAttribute( 'position' ); + + if ( position !== undefined ) { + + for ( let 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; + + } + + } // + + + const numberOfTriangles = index.count - 2; + const newIndices = []; + + if ( drawMode === THREE.TriangleFanDrawMode ) { + + // gl.TRIANGLE_FAN + for ( let 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 ( let 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 - }(); - return GLTFLoader; + const newGeometry = geometry.clone(); + newGeometry.setIndex( newIndices ); + return newGeometry; - }(); + } THREE.GLTFLoader = GLTFLoader; diff --git a/examples/js/loaders/HDRCubeTextureLoader.js b/examples/js/loaders/HDRCubeTextureLoader.js index 66fb5f30a46c91..f06f4539e37bb9 100644 --- a/examples/js/loaders/HDRCubeTextureLoader.js +++ b/examples/js/loaders/HDRCubeTextureLoader.js @@ -1,16 +1,16 @@ ( function () { - var HDRCubeTextureLoader = function ( manager ) { + class HDRCubeTextureLoader extends THREE.Loader { - THREE.Loader.call( this, manager ); - this.hdrLoader = new THREE.RGBELoader(); - this.type = THREE.UnsignedByteType; + constructor( manager ) { - }; + super( manager ); + this.hdrLoader = new THREE.RGBELoader(); + this.type = THREE.UnsignedByteType; - HDRCubeTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { - constructor: HDRCubeTextureLoader, - load: function ( urls, onLoad, onProgress, onError ) { + } + + load( urls, onLoad, onProgress, onError ) { if ( ! Array.isArray( urls ) ) { @@ -23,7 +23,7 @@ } - var texture = new THREE.CubeTexture(); + const texture = new THREE.CubeTexture(); texture.type = this.type; switch ( texture.type ) { @@ -54,20 +54,20 @@ } - var scope = this; - var loaded = 0; + const scope = this; + let loaded = 0; function loadHDRData( i, onLoad, onProgress, onError ) { new THREE.FileLoader( scope.manager ).setPath( scope.path ).setResponseType( 'arraybuffer' ).setWithCredentials( scope.withCredentials ).load( urls[ i ], function ( buffer ) { loaded ++; - var texData = scope.hdrLoader.parse( buffer ); + const texData = scope.hdrLoader.parse( buffer ); if ( ! texData ) return; if ( texData.data !== undefined ) { - var dataTexture = new THREE.DataTexture( texData.data, texData.width, texData.height ); + const dataTexture = new THREE.DataTexture( texData.data, texData.width, texData.height ); dataTexture.type = texture.type; dataTexture.encoding = texture.encoding; dataTexture.format = texture.format; @@ -89,7 +89,7 @@ } - for ( var i = 0; i < urls.length; i ++ ) { + for ( let i = 0; i < urls.length; i ++ ) { loadHDRData( i, onLoad, onProgress, onError ); @@ -97,15 +97,17 @@ return texture; - }, - setDataType: function ( value ) { + } + + setDataType( value ) { this.type = value; this.hdrLoader.setDataType( value ); return this; } - } ); + + } THREE.HDRCubeTextureLoader = HDRCubeTextureLoader; diff --git a/examples/js/loaders/KTXLoader.js b/examples/js/loaders/KTXLoader.js index 618cf9ced4362f..eaf639243ba79a 100644 --- a/examples/js/loaders/KTXLoader.js +++ b/examples/js/loaders/KTXLoader.js @@ -7,17 +7,17 @@ * ported from https://github.com/BabylonJS/Babylon.js/blob/master/src/Tools/babylon.khronosTextureContainer.ts */ - var KTXLoader = function ( manager ) { + class KTXLoader extends THREE.CompressedTextureLoader { - THREE.CompressedTextureLoader.call( this, manager ); + constructor( manager ) { - }; + super( manager ); - KTXLoader.prototype = Object.assign( Object.create( THREE.CompressedTextureLoader.prototype ), { - constructor: KTXLoader, - parse: function ( buffer, loadMipmaps ) { + } + + parse( buffer, loadMipmaps ) { - var ktx = new KhronosTextureContainer( buffer, 1 ); + const ktx = new KhronosTextureContainer( buffer, 1 ); return { mipmaps: ktx.mipmaps( loadMipmaps ), width: ktx.pixelWidth, @@ -28,9 +28,18 @@ }; } - } ); - var KhronosTextureContainer = function () { + } + + const HEADER_LEN = 12 + 13 * 4; // identifier + header elements (not including key value meta-data pairs) + // load types + + const COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D() + //const COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D() + //const TEX_2D = 2; // uses a gl.texImage2D() + //const TEX_3D = 3; // uses a gl.texImage3D() + + class KhronosTextureContainer { /** * @param {ArrayBuffer} arrayBuffer- contents of the KTX container file @@ -38,7 +47,7 @@ * @param {boolean} threeDExpected- provision for indicating that data should be a 3D texture, not implemented * @param {boolean} textureArrayExpected- provision for indicating that data should be a texture array, not implemented */ - function KhronosTextureContainer( arrayBuffer, facesExpected + constructor( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) { @@ -46,7 +55,7 @@ // '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n' // 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A - var identifier = new Uint8Array( this.arrayBuffer, 0, 12 ); + const identifier = new Uint8Array( this.arrayBuffer, 0, 12 ); if ( identifier[ 0 ] !== 0xAB || identifier[ 1 ] !== 0x4B || identifier[ 2 ] !== 0x54 || identifier[ 3 ] !== 0x58 || identifier[ 4 ] !== 0x20 || identifier[ 5 ] !== 0x31 || identifier[ 6 ] !== 0x31 || identifier[ 7 ] !== 0xBB || identifier[ 8 ] !== 0x0D || identifier[ 9 ] !== 0x0A || identifier[ 10 ] !== 0x1A || identifier[ 11 ] !== 0x0A ) { @@ -56,10 +65,10 @@ } // load the reset of the header in native 32 bit uint - var dataSize = Uint32Array.BYTES_PER_ELEMENT; - var headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize ); - var endianness = headerDataView.getUint32( 0, true ); - var littleEndian = endianness === 0x04030201; + const dataSize = Uint32Array.BYTES_PER_ELEMENT; + const headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize ); + const endianness = headerDataView.getUint32( 0, true ); + const littleEndian = endianness === 0x04030201; this.glType = headerDataView.getUint32( 1 * dataSize, littleEndian ); // must be 0 for compressed textures this.glTypeSize = headerDataView.getUint32( 2 * dataSize, littleEndian ); // must be 1 for compressed textures @@ -120,29 +129,28 @@ // would need to make this more elaborate & adjust checks above to support more than one load type - this.loadType = KhronosTextureContainer.COMPRESSED_2D; - - } // return mipmaps for js + this.loadType = COMPRESSED_2D; + } - KhronosTextureContainer.prototype.mipmaps = function ( loadMipmaps ) { + mipmaps( loadMipmaps ) { - var mipmaps = []; // initialize width & height for level 1 + const mipmaps = []; // initialize width & height for level 1 - var dataOffset = KhronosTextureContainer.HEADER_LEN + this.bytesOfKeyValueData; - var width = this.pixelWidth; - var height = this.pixelHeight; - var mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1; + let dataOffset = HEADER_LEN + this.bytesOfKeyValueData; + let width = this.pixelWidth; + let height = this.pixelHeight; + const mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1; - for ( var level = 0; level < mipmapCount; level ++ ) { + for ( let level = 0; level < mipmapCount; level ++ ) { - var imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps + const imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps dataOffset += 4; // size of the image + 4 for the imageSize field - for ( var face = 0; face < this.numberOfFaces; face ++ ) { + for ( let face = 0; face < this.numberOfFaces; face ++ ) { - var byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize ); + const byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize ); mipmaps.push( { 'data': byteArray, 'width': width, @@ -160,22 +168,9 @@ return mipmaps; - }; - - KhronosTextureContainer.HEADER_LEN = 12 + 13 * 4; // identifier + header elements (not including key value meta-data pairs) - // load types - - KhronosTextureContainer.COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D() - - KhronosTextureContainer.COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D() - - KhronosTextureContainer.TEX_2D = 2; // uses a gl.texImage2D() - - KhronosTextureContainer.TEX_3D = 3; // uses a gl.texImage3D() - - return KhronosTextureContainer; + } - }(); + } THREE.KTXLoader = KTXLoader; diff --git a/examples/js/loaders/LWOLoader.js b/examples/js/loaders/LWOLoader.js index fec70da09073c0..47559a9f6981d0 100644 --- a/examples/js/loaders/LWOLoader.js +++ b/examples/js/loaders/LWOLoader.js @@ -12,25 +12,25 @@ * http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo2.html * **/ - var lwoTree; - var LWOLoader = function ( manager, parameters ) { + let _lwoTree; - THREE.Loader.call( this, manager ); - parameters = parameters || {}; - this.resourcePath = parameters.resourcePath !== undefined ? parameters.resourcePath : ''; + class LWOLoader extends THREE.Loader { - }; + constructor( manager, parameters = {} ) { - LWOLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { - constructor: LWOLoader, - load: function ( url, onLoad, onProgress, onError ) { + super( manager ); + this.resourcePath = parameters.resourcePath !== undefined ? parameters.resourcePath : ''; - var scope = this; - var path = scope.path === '' ? extractParentUrl( url, 'Objects' ) : scope.path; // give the mesh a default name based on the filename + } + + load( url, onLoad, onProgress, onError ) { - var modelName = url.split( path ).pop().split( '.' )[ 0 ]; - var loader = new THREE.FileLoader( this.manager ); + const scope = this; + const path = scope.path === '' ? extractParentUrl( url, 'Objects' ) : scope.path; // give the mesh a default name based on the filename + + const modelName = url.split( path ).pop().split( '.' )[ 0 ]; + const loader = new THREE.FileLoader( this.manager ); loader.setPath( scope.path ); loader.setResponseType( 'arraybuffer' ); loader.load( url, function ( buffer ) { @@ -58,26 +58,29 @@ }, onProgress, onError ); - }, - parse: function ( iffBuffer, path, modelName ) { + } - lwoTree = new THREE.IFFParser().parse( iffBuffer ); // console.log( 'lwoTree', lwoTree ); + parse( iffBuffer, path, modelName ) { - var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); + _lwoTree = new THREE.IFFParser().parse( iffBuffer ); // console.log( 'lwoTree', lwoTree ); + + const textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); return new LWOTreeParser( textureLoader ).parse( modelName ); } - } ); // Parse the lwoTree object - function LWOTreeParser( textureLoader ) { + } // Parse the lwoTree object - this.textureLoader = textureLoader; - } + class LWOTreeParser { + + constructor( textureLoader ) { - LWOTreeParser.prototype = { - constructor: LWOTreeParser, - parse: function ( modelName ) { + this.textureLoader = textureLoader; + + } + + parse( modelName ) { this.materials = new MaterialParser( this.textureLoader ).parse(); this.defaultLayerName = modelName; @@ -87,56 +90,58 @@ meshes: this.meshes }; - }, + } parseLayers() { // array of all meshes for building hierarchy - var meshes = []; // final array containing meshes with scene graph hierarchy set up + const meshes = []; // final array containing meshes with scene graph hierarchy set up - var finalMeshes = []; - var geometryParser = new GeometryParser(); - var scope = this; - lwoTree.layers.forEach( function ( layer ) { + const finalMeshes = []; + const geometryParser = new GeometryParser(); + const scope = this; - var geometry = geometryParser.parse( layer.geometry, layer ); - var mesh = scope.parseMesh( geometry, layer ); + _lwoTree.layers.forEach( function ( layer ) { + + const geometry = geometryParser.parse( layer.geometry, layer ); + const mesh = scope.parseMesh( geometry, layer ); meshes[ layer.number ] = mesh; if ( layer.parent === - 1 ) finalMeshes.push( mesh ); else meshes[ layer.parent ].add( mesh ); } ); + this.applyPivots( finalMeshes ); return finalMeshes; - }, + } parseMesh( geometry, layer ) { - var mesh; - var materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type ); + let mesh; + const materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type ); this.duplicateUVs( geometry, materials ); if ( layer.geometry.type === 'points' ) mesh = new THREE.Points( geometry, materials ); else if ( layer.geometry.type === 'lines' ) mesh = new THREE.LineSegments( geometry, materials ); else mesh = new THREE.Mesh( geometry, materials ); if ( layer.name ) mesh.name = layer.name; else mesh.name = this.defaultLayerName + '_layer_' + layer.number; mesh.userData.pivot = layer.pivot; return mesh; - }, + } // TODO: may need to be reversed in z to convert LWO to three.js coordinates + - // TODO: may need to be reversed in z to convert LWO to three.js coordinates applyPivots( meshes ) { meshes.forEach( function ( mesh ) { mesh.traverse( function ( child ) { - var pivot = child.userData.pivot; + const pivot = child.userData.pivot; child.position.x += pivot[ 0 ]; child.position.y += pivot[ 1 ]; child.position.z += pivot[ 2 ]; if ( child.parent ) { - var parentPivot = child.parent.userData.pivot; + const parentPivot = child.parent.userData.pivot; child.position.x -= parentPivot[ 0 ]; child.position.y -= parentPivot[ 1 ]; child.position.z -= parentPivot[ 2 ]; @@ -147,12 +152,12 @@ } ); - }, + } getMaterials( namesArray, type ) { - var materials = []; - var scope = this; + const materials = []; + const scope = this; namesArray.forEach( function ( name, i ) { materials[ i ] = scope.getMaterialByName( name ); @@ -163,7 +168,7 @@ materials.forEach( function ( mat, i ) { - var spec = { + const spec = { color: mat.color }; @@ -185,11 +190,11 @@ } // if there is only one material, return that directly instead of array - var filtered = materials.filter( Boolean ); + const filtered = materials.filter( Boolean ); if ( filtered.length === 1 ) return filtered[ 0 ]; return materials; - }, + } getMaterialByName( name ) { @@ -199,12 +204,12 @@ } )[ 0 ]; - }, + } // If the material has an aoMap, duplicate UVs + - // If the material has an aoMap, duplicate UVs duplicateUVs( geometry, materials ) { - var duplicateUVs = false; + let duplicateUVs = false; if ( ! Array.isArray( materials ) ) { @@ -225,30 +230,30 @@ } - }; + } - function MaterialParser( textureLoader ) { + class MaterialParser { - this.textureLoader = textureLoader; + constructor( textureLoader ) { - } + this.textureLoader = textureLoader; + + } - MaterialParser.prototype = { - constructor: MaterialParser, - parse: function () { + parse() { - var materials = []; + const materials = []; this.textures = {}; - for ( var name in lwoTree.materials ) { + for ( const name in _lwoTree.materials ) { - if ( lwoTree.format === 'LWO3' ) { + if ( _lwoTree.format === 'LWO3' ) { - materials.push( this.parseMaterial( lwoTree.materials[ name ], name, lwoTree.textures ) ); + materials.push( this.parseMaterial( _lwoTree.materials[ name ], name, _lwoTree.textures ) ); - } else if ( lwoTree.format === 'LWO2' ) { + } else if ( _lwoTree.format === 'LWO2' ) { - materials.push( this.parseMaterialLwo2( lwoTree.materials[ name ], name, lwoTree.textures ) ); + materials.push( this.parseMaterialLwo2( _lwoTree.materials[ name ], name, _lwoTree.textures ) ); } @@ -256,45 +261,45 @@ return materials; - }, + } parseMaterial( materialData, name, textures ) { - var params = { + let params = { name: name, side: this.getSide( materialData.attributes ), flatShading: this.getSmooth( materialData.attributes ) }; - var connections = this.parseConnections( materialData.connections, materialData.nodes ); - var maps = this.parseTextureNodes( connections.maps ); + const connections = this.parseConnections( materialData.connections, materialData.nodes ); + const maps = this.parseTextureNodes( connections.maps ); this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps ); - var attributes = this.parseAttributes( connections.attributes, maps ); + const attributes = this.parseAttributes( connections.attributes, maps ); this.parseEnvMap( connections, maps, attributes ); params = Object.assign( maps, params ); params = Object.assign( params, attributes ); - var materialType = this.getMaterialType( connections.attributes ); + const materialType = this.getMaterialType( connections.attributes ); return new materialType( params ); - }, + } parseMaterialLwo2( materialData, name /*, textures*/ ) { - var params = { + let params = { name: name, side: this.getSide( materialData.attributes ), flatShading: this.getSmooth( materialData.attributes ) }; - var attributes = this.parseAttributes( materialData.attributes, {} ); + const attributes = this.parseAttributes( materialData.attributes, {} ); params = Object.assign( params, attributes ); return new THREE.MeshPhongMaterial( params ); - }, - - // Note: converting from left to right handed coords by switching x -> -x in vertices, and + } // Note: converting from left to right handed coords by switching x -> -x in vertices, and // then switching mat THREE.FrontSide -> THREE.BackSide // NB: this means that THREE.FrontSide and THREE.BackSide have been switched! + + getSide( attributes ) { if ( ! attributes.side ) return THREE.BackSide; @@ -313,29 +318,29 @@ } - }, + } getSmooth( attributes ) { if ( ! attributes.smooth ) return true; return ! attributes.smooth; - }, + } parseConnections( connections, nodes ) { - var materialConnections = { + const materialConnections = { maps: {} }; - var inputName = connections.inputName; - var inputNodeName = connections.inputNodeName; - var nodeName = connections.nodeName; - var scope = this; + const inputName = connections.inputName; + const inputNodeName = connections.inputNodeName; + const nodeName = connections.nodeName; + const scope = this; inputName.forEach( function ( name, index ) { if ( name === 'Material' ) { - var matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes ); + const matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes ); materialConnections.attributes = matNode.attributes; materialConnections.envMap = matNode.fileName; materialConnections.name = inputNodeName[ index ]; @@ -354,28 +359,28 @@ } ); return materialConnections; - }, + } getNodeByRefName( refName, nodes ) { - for ( var name in nodes ) { + for ( const name in nodes ) { if ( nodes[ name ].refName === refName ) return nodes[ name ]; } - }, + } parseTextureNodes( textureNodes ) { - var maps = {}; + const maps = {}; - for ( var name in textureNodes ) { + for ( const name in textureNodes ) { - var node = textureNodes[ name ]; - var path = node.fileName; + const node = textureNodes[ name ]; + const path = node.fileName; if ( ! path ) return; - var texture = this.loadTexture( path ); + const texture = this.loadTexture( path ); if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode ); if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode ); @@ -432,22 +437,22 @@ if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap; return maps; - }, - - // maps can also be defined on individual material attributes, parse those here + } // maps can also be defined on individual material attributes, parse those here // This occurs on Standard (Phong) surfaces + + parseAttributeImageMaps( attributes, textures, maps ) { - for ( var name in attributes ) { + for ( const name in attributes ) { - var attribute = attributes[ name ]; + const attribute = attributes[ name ]; if ( attribute.maps ) { - var mapData = attribute.maps[ 0 ]; - var path = this.getTexturePathByIndex( mapData.imageIndex, textures ); + const mapData = attribute.maps[ 0 ]; + const path = this.getTexturePathByIndex( mapData.imageIndex, textures ); if ( ! path ) return; - var texture = this.loadTexture( path ); + const texture = this.loadTexture( path ); if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w ); if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h ); @@ -501,11 +506,11 @@ } - }, + } parseAttributes( attributes, maps ) { - var params = {}; // don't use color data if color map is present + const params = {}; // don't use color data if color map is present if ( attributes.Color && ! maps.map ) { @@ -527,7 +532,7 @@ this.parsePhongAttributes( params, attributes, maps ); return params; - }, + } parsePhysicalAttributes( params, attributes /*, maps*/ @@ -545,7 +550,7 @@ } - }, + } parseStandardAttributes( params, attributes, maps ) { @@ -568,7 +573,7 @@ if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value; if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value; - }, + } parsePhongAttributes( params, attributes, maps ) { @@ -614,13 +619,13 @@ if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 ); - }, + } parseEnvMap( connections, maps, attributes ) { if ( connections.envMap ) { - var envMap = this.loadTexture( connections.envMap ); + const envMap = this.loadTexture( connections.envMap ); if ( attributes.transparent && attributes.opacity < 0.999 ) { @@ -645,36 +650,37 @@ } - }, + } // get texture defined at top level by its index + - // get texture defined at top level by its index getTexturePathByIndex( index ) { - var fileName = ''; - if ( ! lwoTree.textures ) return fileName; - lwoTree.textures.forEach( function ( texture ) { + let fileName = ''; + if ( ! _lwoTree.textures ) return fileName; + + _lwoTree.textures.forEach( function ( texture ) { if ( texture.index === index ) fileName = texture.fileName; } ); + return fileName; - }, + } loadTexture( path ) { if ( ! path ) return null; - var texture; - texture = this.textureLoader.load( path, undefined, undefined, function () { + const texture = this.textureLoader.load( path, undefined, undefined, function () { console.warn( 'LWOLoader: non-standard resource hierarchy. Use \`resourcePath\` parameter to specify root content directory.' ); } ); return texture; - }, + } // 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge + - // 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge getWrappingType( num ) { switch ( num ) { @@ -694,7 +700,7 @@ } - }, + } getMaterialType( nodeData ) { @@ -704,42 +710,39 @@ } - }; - - function GeometryParser() {} + } - GeometryParser.prototype = { - constructor: GeometryParser, + class GeometryParser { parse( geoData, layer ) { - var geometry = new THREE.BufferGeometry(); + const geometry = new THREE.BufferGeometry(); geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geoData.points, 3 ) ); - var indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions ); + const indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions ); geometry.setIndex( indices ); this.parseGroups( geometry, geoData ); geometry.computeVertexNormals(); this.parseUVs( geometry, layer, indices ); this.parseMorphTargets( geometry, layer, indices ); // TODO: z may need to be reversed to account for coordinate system change - geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); // var userData = geometry.userData; + geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); // let userData = geometry.userData; // geometry = geometry.toNonIndexed() // geometry.userData = userData; return geometry; - }, + } // split quads into tris + - // split quads into tris splitIndices( indices, polygonDimensions ) { - var remappedIndices = []; - var i = 0; + const remappedIndices = []; + let i = 0; polygonDimensions.forEach( function ( dim ) { if ( dim < 4 ) { - for ( var k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] ); + for ( let k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] ); } else if ( dim === 4 ) { @@ -747,7 +750,7 @@ } else if ( dim > 4 ) { - for ( var k = 1; k < dim - 1; k ++ ) { + for ( let k = 1; k < dim - 1; k ++ ) { remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] ); @@ -762,34 +765,35 @@ } ); return remappedIndices; - }, + } // NOTE: currently ignoring poly indices and assuming that they are intelligently ordered + - // NOTE: currently ignoring poly indices and assuming that they are intelligently ordered parseGroups( geometry, geoData ) { - var tags = lwoTree.tags; - var matNames = []; - var elemSize = 3; + const tags = _lwoTree.tags; + const matNames = []; + let elemSize = 3; if ( geoData.type === 'lines' ) elemSize = 2; if ( geoData.type === 'points' ) elemSize = 1; - var remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices ); - var indexNum = 0; // create new indices in numerical order + const remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices ); + let indexNum = 0; // create new indices in numerical order - var indexPairs = {}; // original indices mapped to numerical indices + const indexPairs = {}; // original indices mapped to numerical indices - var prevMaterialIndex; - var prevStart = 0; - var currentCount = 0; + let prevMaterialIndex; + let materialIndex; + let prevStart = 0; + let currentCount = 0; - for ( var i = 0; i < remappedIndices.length; i += 2 ) { + for ( let i = 0; i < remappedIndices.length; i += 2 ) { - var materialIndex = remappedIndices[ i + 1 ]; + materialIndex = remappedIndices[ i + 1 ]; if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ]; if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex; if ( materialIndex !== prevMaterialIndex ) { - var currentIndex; + let currentIndex; if ( indexPairs[ tags[ prevMaterialIndex ] ] ) { @@ -818,7 +822,7 @@ if ( geometry.groups.length > 0 ) { - var currentIndex; + let currentIndex; if ( indexPairs[ tags[ materialIndex ] ] ) { @@ -839,11 +843,11 @@ geometry.userData.matNames = matNames; - }, + } splitMaterialIndices( polygonDimensions, indices ) { - var remappedIndices = []; + const remappedIndices = []; polygonDimensions.forEach( function ( dim, i ) { if ( dim <= 3 ) { @@ -857,7 +861,7 @@ } else { // ignore > 4 for now - for ( var k = 0; k < dim - 2; k ++ ) { + for ( let k = 0; k < dim - 2; k ++ ) { remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] ); @@ -868,9 +872,7 @@ } ); return remappedIndices; - }, - - // UV maps: + } // UV maps: // 1: are defined via index into an array of points, not into a geometry // - the geometry is also defined by an index into this array, but the indexes may not match // 2: there can be any number of UV maps for a single geometry. Here these are combined, @@ -879,19 +881,21 @@ // 4: UV maps can be VMAP or VMAD (discontinuous, to allow for seams). In practice, most // UV maps are defined as partially VMAP and partially VMAD // VMADs are currently not supported + + parseUVs( geometry, layer ) { // start by creating a UV map set to zero for the whole geometry - var remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () { + const remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () { return 0; } ); - for ( var name in layer.uvs ) { + for ( const name in layer.uvs ) { - var uvs = layer.uvs[ name ].uvs; - var uvIndices = layer.uvs[ name ].uvIndices; + const uvs = layer.uvs[ name ].uvs; + const uvIndices = layer.uvs[ name ].uvIndices; uvIndices.forEach( function ( i, j ) { remappedUVs[ i * 2 ] = uvs[ j * 2 ]; @@ -903,19 +907,19 @@ geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( remappedUVs, 2 ) ); - }, + } parseMorphTargets( geometry, layer ) { - var num = 0; + let num = 0; - for ( var name in layer.morphTargets ) { + for ( const name in layer.morphTargets ) { - var remappedPoints = geometry.attributes.position.array.slice(); + const remappedPoints = geometry.attributes.position.array.slice(); if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = []; - var morphPoints = layer.morphTargets[ name ].points; - var morphIndices = layer.morphTargets[ name ].indices; - var type = layer.morphTargets[ name ].type; + const morphPoints = layer.morphTargets[ name ].points; + const morphIndices = layer.morphTargets[ name ].indices; + const type = layer.morphTargets[ name ].type; morphIndices.forEach( function ( i, j ) { if ( type === 'relative' ) { @@ -943,11 +947,12 @@ } - }; // ************** UTILITY FUNCTIONS ************** + } // ************** UTILITY FUNCTIONS ************** + function extractParentUrl( url, dir ) { - var index = url.indexOf( dir ); + const index = url.indexOf( dir ); if ( index === - 1 ) return './'; return url.substr( 0, index ); diff --git a/examples/js/loaders/MTLLoader.js b/examples/js/loaders/MTLLoader.js index 0785d2866e325f..a1b4053c205b50 100644 --- a/examples/js/loaders/MTLLoader.js +++ b/examples/js/loaders/MTLLoader.js @@ -4,15 +4,13 @@ * Loads a Wavefront .mtl file specifying materials */ - var MTLLoader = function ( manager ) { + class MTLLoader extends THREE.Loader { - THREE.Loader.call( this, manager ); + constructor( manager ) { - }; - - MTLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { - constructor: MTLLoader, + super( manager ); + } /** * Loads and parses a MTL asset from a URL. * @@ -26,11 +24,13 @@ * @note In order for relative texture references to resolve correctly * you must call setResourcePath() explicitly prior to load. */ - load: function ( url, onLoad, onProgress, onError ) { - var scope = this; - var path = this.path === '' ? THREE.LoaderUtils.extractUrlBase( url ) : this.path; - var loader = new THREE.FileLoader( this.manager ); + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + const path = this.path === '' ? THREE.LoaderUtils.extractUrlBase( url ) : this.path; + const loader = new THREE.FileLoader( this.manager ); loader.setPath( this.path ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( this.withCredentials ); @@ -58,35 +58,37 @@ }, onProgress, onError ); - }, - setMaterialOptions: function ( value ) { + } + + setMaterialOptions( value ) { this.materialOptions = value; return this; - }, - + } /** * Parses a MTL file. * * @param {String} text - Content of MTL file - * @return {MTLLoader.MaterialCreator} + * @return {MaterialCreator} * * @see setPath setResourcePath * * @note In order for relative texture references to resolve correctly * you must call setResourcePath() explicitly prior to parse. */ - parse: function ( text, path ) { - var lines = text.split( '\n' ); - var info = {}; - var delimiter_pattern = /\s+/; - var materialsInfo = {}; - for ( var i = 0; i < lines.length; i ++ ) { + parse( text, path ) { + + const lines = text.split( '\n' ); + let info = {}; + const delimiter_pattern = /\s+/; + const materialsInfo = {}; + + for ( let i = 0; i < lines.length; i ++ ) { - var line = lines[ i ]; + let line = lines[ i ]; line = line.trim(); if ( line.length === 0 || line.charAt( 0 ) === '#' ) { @@ -96,10 +98,10 @@ } - var pos = line.indexOf( ' ' ); - var key = pos >= 0 ? line.substring( 0, pos ) : line; + const pos = line.indexOf( ' ' ); + let key = pos >= 0 ? line.substring( 0, pos ) : line; key = key.toLowerCase(); - var value = pos >= 0 ? line.substring( pos + 1 ) : ''; + let value = pos >= 0 ? line.substring( pos + 1 ) : ''; value = value.trim(); if ( key === 'newmtl' ) { @@ -114,7 +116,7 @@ if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) { - var ss = value.split( delimiter_pattern, 3 ); + const ss = value.split( delimiter_pattern, 3 ); info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; } else { @@ -127,14 +129,15 @@ } - var materialCreator = new MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions ); + const materialCreator = new MaterialCreator( this.resourcePath || path, this.materialOptions ); materialCreator.setCrossOrigin( this.crossOrigin ); materialCreator.setManager( this.manager ); materialCreator.setMaterials( materialsInfo ); return materialCreator; } - } ); + + } /** * Create a new MTLLoader.MaterialCreator * @param baseUrl - Url relative to which textures are loaded @@ -150,58 +153,62 @@ * @constructor */ - MTLLoader.MaterialCreator = function ( baseUrl, options ) { - this.baseUrl = baseUrl || ''; - this.options = options; - this.materialsInfo = {}; - this.materials = {}; - this.materialsArray = []; - this.nameLookup = {}; - this.side = this.options && this.options.side ? this.options.side : THREE.FrontSide; - this.wrap = this.options && this.options.wrap ? this.options.wrap : THREE.RepeatWrapping; + class MaterialCreator { - }; + constructor( baseUrl = '', options = {} ) { - MTLLoader.MaterialCreator.prototype = { - constructor: MTLLoader.MaterialCreator, - crossOrigin: 'anonymous', - setCrossOrigin: function ( value ) { + this.baseUrl = baseUrl; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + this.crossOrigin = 'anonymous'; + this.side = this.options.side !== undefined ? this.options.side : THREE.FrontSide; + this.wrap = this.options.wrap !== undefined ? this.options.wrap : THREE.RepeatWrapping; + + } + + setCrossOrigin( value ) { this.crossOrigin = value; return this; - }, - setManager: function ( value ) { + } + + setManager( value ) { this.manager = value; - }, - setMaterials: function ( materialsInfo ) { + } + + setMaterials( materialsInfo ) { this.materialsInfo = this.convert( materialsInfo ); this.materials = {}; this.materialsArray = []; this.nameLookup = {}; - }, - convert: function ( materialsInfo ) { + } + + convert( materialsInfo ) { if ( ! this.options ) return materialsInfo; - var converted = {}; + const converted = {}; - for ( var mn in materialsInfo ) { + for ( const mn in materialsInfo ) { // Convert materials info into normalized form based on options - var mat = materialsInfo[ mn ]; - var covmat = {}; + const mat = materialsInfo[ mn ]; + const covmat = {}; converted[ mn ] = covmat; - for ( var prop in mat ) { + for ( const prop in mat ) { - var save = true; - var value = mat[ prop ]; - var lprop = prop.toLowerCase(); + let save = true; + let value = mat[ prop ]; + const lprop = prop.toLowerCase(); switch ( lprop ) { @@ -245,26 +252,29 @@ return converted; - }, - preload: function () { + } + + preload() { - for ( var mn in this.materialsInfo ) { + for ( const mn in this.materialsInfo ) { this.create( mn ); } - }, - getIndex: function ( materialName ) { + } + + getIndex( materialName ) { return this.nameLookup[ materialName ]; - }, - getAsArray: function () { + } + + getAsArray() { - var index = 0; + let index = 0; - for ( var mn in this.materialsInfo ) { + for ( const mn in this.materialsInfo ) { this.materialsArray[ index ] = this.create( mn ); this.nameLookup[ mn ] = index; @@ -274,8 +284,9 @@ return this.materialsArray; - }, - create: function ( materialName ) { + } + + create( materialName ) { if ( this.materials[ materialName ] === undefined ) { @@ -285,13 +296,14 @@ return this.materials[ materialName ]; - }, - createMaterial_: function ( materialName ) { + } + + createMaterial_( materialName ) { // Create material - var scope = this; - var mat = this.materialsInfo[ materialName ]; - var params = { + const scope = this; + const mat = this.materialsInfo[ materialName ]; + const params = { name: materialName, side: this.side }; @@ -309,8 +321,8 @@ if ( params[ mapType ] ) return; // Keep the first encountered texture - var texParams = scope.getTextureParams( value, params ); - var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); + const texParams = scope.getTextureParams( value, params ); + const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); map.repeat.copy( texParams.scale ); map.offset.copy( texParams.offset ); map.wrapS = scope.wrap; @@ -319,10 +331,10 @@ } - for ( var prop in mat ) { + for ( const prop in mat ) { - var value = mat[ prop ]; - var n; + const value = mat[ prop ]; + let n; if ( value === '' ) continue; switch ( prop.toLowerCase() ) { @@ -415,15 +427,16 @@ this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); return this.materials[ materialName ]; - }, - getTextureParams: function ( value, matParams ) { + } - var texParams = { + getTextureParams( value, matParams ) { + + const texParams = { scale: new THREE.Vector2( 1, 1 ), offset: new THREE.Vector2( 0, 0 ) }; - var items = value.split( /\s+/ ); - var pos; + const items = value.split( /\s+/ ); + let pos; pos = items.indexOf( '-bm' ); if ( pos >= 0 ) { @@ -454,12 +467,12 @@ texParams.url = items.join( ' ' ).trim(); return texParams; - }, - loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { + } + + loadTexture( url, mapping, onLoad, onProgress, onError ) { - var texture; - var manager = this.manager !== undefined ? this.manager : THREE.DefaultLoadingManager; - var loader = manager.getHandler( url ); + const manager = this.manager !== undefined ? this.manager : THREE.DefaultLoadingManager; + let loader = manager.getHandler( url ); if ( loader === null ) { @@ -468,12 +481,13 @@ } if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); - texture = loader.load( url, onLoad, onProgress, onError ); + const texture = loader.load( url, onLoad, onProgress, onError ); if ( mapping !== undefined ) texture.mapping = mapping; return texture; } - }; + + } THREE.MTLLoader = MTLLoader; diff --git a/examples/js/loaders/OBJLoader.js b/examples/js/loaders/OBJLoader.js index 896c05a5f18e0a..a0a7c67952414d 100644 --- a/examples/js/loaders/OBJLoader.js +++ b/examples/js/loaders/OBJLoader.js @@ -1,575 +1,587 @@ ( function () { - var OBJLoader = function () { + const _object_pattern = /^[og]\s*(.+)?/; // mtllib file_reference - // o object_name | g group_name - var object_pattern = /^[og]\s*(.+)?/; // mtllib file_reference + const _material_library_pattern = /^mtllib /; // usemtl material_name - var material_library_pattern = /^mtllib /; // usemtl material_name + const _material_use_pattern = /^usemtl /; // usemap map_name - var material_use_pattern = /^usemtl /; // usemap map_name + const _map_use_pattern = /^usemap /; - var map_use_pattern = /^usemap /; - var vA = new THREE.Vector3(); - var vB = new THREE.Vector3(); - var vC = new THREE.Vector3(); - var ab = new THREE.Vector3(); - var cb = new THREE.Vector3(); + const _vA = new THREE.Vector3(); - function ParserState() { + const _vB = new THREE.Vector3(); - var state = { - objects: [], - object: {}, - vertices: [], - normals: [], - colors: [], - uvs: [], - materials: {}, - materialLibraries: [], - startObject: function ( name, fromDeclaration ) { + const _vC = new THREE.Vector3(); - // If the current object (initial from reset) is not from a g/o declaration in the parsed - // file. We need to use it for the first parsed g/o to keep things in sync. - if ( this.object && this.object.fromDeclaration === false ) { + const _ab = new THREE.Vector3(); - this.object.name = name; - this.object.fromDeclaration = fromDeclaration !== false; - return; + const _cb = new THREE.Vector3(); - } + function ParserState() { - var previousMaterial = this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined; + const state = { + objects: [], + object: {}, + vertices: [], + normals: [], + colors: [], + uvs: [], + materials: {}, + materialLibraries: [], + startObject: function ( name, fromDeclaration ) { - if ( this.object && typeof this.object._finalize === 'function' ) { + // If the current object (initial from reset) is not from a g/o declaration in the parsed + // file. We need to use it for the first parsed g/o to keep things in sync. + if ( this.object && this.object.fromDeclaration === false ) { - this.object._finalize( true ); + this.object.name = name; + this.object.fromDeclaration = fromDeclaration !== false; + return; - } + } - this.object = { - name: name || '', - fromDeclaration: fromDeclaration !== false, - geometry: { - vertices: [], - normals: [], - colors: [], - uvs: [], - hasUVIndices: false - }, - materials: [], - smooth: true, - startMaterial: function ( name, libraries ) { + const previousMaterial = this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined; - var previous = this._finalize( false ); // New usemtl declaration overwrites an inherited material, except if faces were declared - // after the material, then it must be preserved for proper MultiMaterial continuation. + if ( this.object && typeof this.object._finalize === 'function' ) { + this.object._finalize( true ); - if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { + } - this.materials.splice( previous.index, 1 ); + this.object = { + name: name || '', + fromDeclaration: fromDeclaration !== false, + geometry: { + vertices: [], + normals: [], + colors: [], + uvs: [], + hasUVIndices: false + }, + materials: [], + smooth: true, + startMaterial: function ( name, libraries ) { - } + const previous = this._finalize( false ); // New usemtl declaration overwrites an inherited material, except if faces were declared + // after the material, then it must be preserved for proper MultiMaterial continuation. - var material = { - index: this.materials.length, - name: name || '', - mtllib: Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '', - smooth: previous !== undefined ? previous.smooth : this.smooth, - groupStart: previous !== undefined ? previous.groupEnd : 0, - groupEnd: - 1, - groupCount: - 1, - inherited: false, - clone: function ( index ) { - - var cloned = { - index: typeof index === 'number' ? index : this.index, - name: this.name, - mtllib: this.mtllib, - smooth: this.smooth, - groupStart: 0, - groupEnd: - 1, - groupCount: - 1, - inherited: false - }; - cloned.clone = this.clone.bind( cloned ); - return cloned; - } - }; - this.materials.push( material ); - return material; + if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { - }, - currentMaterial: function () { + this.materials.splice( previous.index, 1 ); - if ( this.materials.length > 0 ) { + } - return this.materials[ this.materials.length - 1 ]; + const material = { + index: this.materials.length, + name: name || '', + mtllib: Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '', + smooth: previous !== undefined ? previous.smooth : this.smooth, + groupStart: previous !== undefined ? previous.groupEnd : 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false, + clone: function ( index ) { + + const cloned = { + index: typeof index === 'number' ? index : this.index, + name: this.name, + mtllib: this.mtllib, + smooth: this.smooth, + groupStart: 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false + }; + cloned.clone = this.clone.bind( cloned ); + return cloned; } + }; + this.materials.push( material ); + return material; - return undefined; + }, + currentMaterial: function () { - }, - _finalize: function ( end ) { + if ( this.materials.length > 0 ) { - var lastMultiMaterial = this.currentMaterial(); + return this.materials[ this.materials.length - 1 ]; - if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { - - lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; - lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; - lastMultiMaterial.inherited = false; + } - } // Ignore objects tail materials if no face declarations followed them before a new o/g started. + return undefined; + }, + _finalize: function ( end ) { - if ( end && this.materials.length > 1 ) { + const lastMultiMaterial = this.currentMaterial(); - for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { - if ( this.materials[ mi ].groupCount <= 0 ) { + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + lastMultiMaterial.inherited = false; - this.materials.splice( mi, 1 ); + } // Ignore objects tail materials if no face declarations followed them before a new o/g started. - } - } + if ( end && this.materials.length > 1 ) { - } // Guarantee at least one empty material, this makes the creation later more straight forward. + for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) { + if ( this.materials[ mi ].groupCount <= 0 ) { - if ( end && this.materials.length === 0 ) { + this.materials.splice( mi, 1 ); - this.materials.push( { - name: '', - smooth: this.smooth - } ); + } } - return lastMultiMaterial; + } // Guarantee at least one empty material, this makes the creation later more straight forward. - } - }; // Inherit previous objects material. - // Spec tells us that a declared material must be set to all objects until a new material is declared. - // If a usemtl declaration is encountered while this new object is being parsed, it will - // overwrite the inherited material. Exception being that there was already face declarations - // to the inherited material, then it will be preserved for proper MultiMaterial continuation. - if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { + if ( end && this.materials.length === 0 ) { - var declared = previousMaterial.clone( 0 ); - declared.inherited = true; - this.object.materials.push( declared ); + this.materials.push( { + name: '', + smooth: this.smooth + } ); + + } + + return lastMultiMaterial; } + }; // Inherit previous objects material. + // Spec tells us that a declared material must be set to all objects until a new material is declared. + // If a usemtl declaration is encountered while this new object is being parsed, it will + // overwrite the inherited material. Exception being that there was already face declarations + // to the inherited material, then it will be preserved for proper MultiMaterial continuation. - this.objects.push( this.object ); + if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { - }, - finalize: function () { + const declared = previousMaterial.clone( 0 ); + declared.inherited = true; + this.object.materials.push( declared ); - if ( this.object && typeof this.object._finalize === 'function' ) { + } - this.object._finalize( true ); + this.objects.push( this.object ); - } + }, + finalize: function () { - }, - parseVertexIndex: function ( value, len ) { + if ( this.object && typeof this.object._finalize === 'function' ) { - var index = parseInt( value, 10 ); - return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + this.object._finalize( true ); - }, - parseNormalIndex: function ( value, len ) { + } - var index = parseInt( value, 10 ); - return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + }, + parseVertexIndex: function ( value, len ) { - }, - parseUVIndex: function ( value, len ) { - - var index = parseInt( value, 10 ); - return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; - }, - addVertex: function ( a, b, c ) { + }, + parseNormalIndex: function ( value, len ) { - var src = this.vertices; - var dst = this.object.geometry.vertices; - dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); - dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; - }, - addVertexPoint: function ( a ) { + }, + parseUVIndex: function ( value, len ) { - var src = this.vertices; - var dst = this.object.geometry.vertices; - dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; - }, - addVertexLine: function ( a ) { + }, + addVertex: function ( a, b, c ) { - var src = this.vertices; - var dst = this.object.geometry.vertices; - dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + const src = this.vertices; + const dst = this.object.geometry.vertices; + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); - }, - addNormal: function ( a, b, c ) { + }, + addVertexPoint: function ( a ) { - var src = this.normals; - var dst = this.object.geometry.normals; - dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); - dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + const src = this.vertices; + const dst = this.object.geometry.vertices; + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - }, - addFaceNormal: function ( a, b, c ) { + }, + addVertexLine: function ( a ) { - var src = this.vertices; - var dst = this.object.geometry.normals; - vA.fromArray( src, a ); - vB.fromArray( src, b ); - vC.fromArray( src, c ); - cb.subVectors( vC, vB ); - ab.subVectors( vA, vB ); - cb.cross( ab ); - cb.normalize(); - dst.push( cb.x, cb.y, cb.z ); - dst.push( cb.x, cb.y, cb.z ); - dst.push( cb.x, cb.y, cb.z ); + const src = this.vertices; + const dst = this.object.geometry.vertices; + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - }, - addColor: function ( a, b, c ) { + }, + addNormal: function ( a, b, c ) { - var src = this.colors; - var dst = this.object.geometry.colors; - if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); - if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + const src = this.normals; + const dst = this.object.geometry.normals; + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); - }, - addUV: function ( a, b, c ) { + }, + addFaceNormal: function ( a, b, c ) { - var src = this.uvs; - var dst = this.object.geometry.uvs; - dst.push( src[ a + 0 ], src[ a + 1 ] ); - dst.push( src[ b + 0 ], src[ b + 1 ] ); - dst.push( src[ c + 0 ], src[ c + 1 ] ); + const src = this.vertices; + const dst = this.object.geometry.normals; - }, - addDefaultUV: function () { + _vA.fromArray( src, a ); - var dst = this.object.geometry.uvs; - dst.push( 0, 0 ); - dst.push( 0, 0 ); - dst.push( 0, 0 ); + _vB.fromArray( src, b ); - }, - addUVLine: function ( a ) { + _vC.fromArray( src, c ); - var src = this.uvs; - var dst = this.object.geometry.uvs; - dst.push( src[ a + 0 ], src[ a + 1 ] ); + _cb.subVectors( _vC, _vB ); - }, - addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { - - var vLen = this.vertices.length; - var ia = this.parseVertexIndex( a, vLen ); - var ib = this.parseVertexIndex( b, vLen ); - var ic = this.parseVertexIndex( c, vLen ); - this.addVertex( ia, ib, ic ); - this.addColor( ia, ib, ic ); // normals - - if ( na !== undefined && na !== '' ) { - - var nLen = this.normals.length; - ia = this.parseNormalIndex( na, nLen ); - ib = this.parseNormalIndex( nb, nLen ); - ic = this.parseNormalIndex( nc, nLen ); - this.addNormal( ia, ib, ic ); + _ab.subVectors( _vA, _vB ); - } else { + _cb.cross( _ab ); - this.addFaceNormal( ia, ib, ic ); + _cb.normalize(); - } // uvs + dst.push( _cb.x, _cb.y, _cb.z ); + dst.push( _cb.x, _cb.y, _cb.z ); + dst.push( _cb.x, _cb.y, _cb.z ); + }, + addColor: function ( a, b, c ) { - if ( ua !== undefined && ua !== '' ) { + const src = this.colors; + const dst = this.object.geometry.colors; + if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); - var uvLen = this.uvs.length; - ia = this.parseUVIndex( ua, uvLen ); - ib = this.parseUVIndex( ub, uvLen ); - ic = this.parseUVIndex( uc, uvLen ); - this.addUV( ia, ib, ic ); - this.object.geometry.hasUVIndices = true; + }, + addUV: function ( a, b, c ) { - } else { + const src = this.uvs; + const dst = this.object.geometry.uvs; + dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ] ); - // add placeholder values (for inconsistent face definitions) - this.addDefaultUV(); + }, + addDefaultUV: function () { - } + const dst = this.object.geometry.uvs; + dst.push( 0, 0 ); + dst.push( 0, 0 ); + dst.push( 0, 0 ); - }, - addPointGeometry: function ( vertices ) { + }, + addUVLine: function ( a ) { - this.object.geometry.type = 'Points'; - var vLen = this.vertices.length; + const src = this.uvs; + const dst = this.object.geometry.uvs; + dst.push( src[ a + 0 ], src[ a + 1 ] ); - for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + }, + addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { - var index = this.parseVertexIndex( vertices[ vi ], vLen ); - this.addVertexPoint( index ); - this.addColor( index ); + const vLen = this.vertices.length; + let ia = this.parseVertexIndex( a, vLen ); + let ib = this.parseVertexIndex( b, vLen ); + let ic = this.parseVertexIndex( c, vLen ); + this.addVertex( ia, ib, ic ); + this.addColor( ia, ib, ic ); // normals - } + if ( na !== undefined && na !== '' ) { - }, - addLineGeometry: function ( vertices, uvs ) { + const nLen = this.normals.length; + ia = this.parseNormalIndex( na, nLen ); + ib = this.parseNormalIndex( nb, nLen ); + ic = this.parseNormalIndex( nc, nLen ); + this.addNormal( ia, ib, ic ); - this.object.geometry.type = 'Line'; - var vLen = this.vertices.length; - var uvLen = this.uvs.length; + } else { - for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + this.addFaceNormal( ia, ib, ic ); - this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); + } // uvs - } - for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { + if ( ua !== undefined && ua !== '' ) { - this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); + const uvLen = this.uvs.length; + ia = this.parseUVIndex( ua, uvLen ); + ib = this.parseUVIndex( ub, uvLen ); + ic = this.parseUVIndex( uc, uvLen ); + this.addUV( ia, ib, ic ); + this.object.geometry.hasUVIndices = true; - } + } else { + + // add placeholder values (for inconsistent face definitions) + this.addDefaultUV(); } - }; - state.startObject( '', false ); - return state; - } // + }, + addPointGeometry: function ( vertices ) { + this.object.geometry.type = 'Points'; + const vLen = this.vertices.length; - function OBJLoader( manager ) { + for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) { - THREE.Loader.call( this, manager ); - this.materials = null; + const index = this.parseVertexIndex( vertices[ vi ], vLen ); + this.addVertexPoint( index ); + this.addColor( index ); - } + } - OBJLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { - constructor: OBJLoader, - load: function ( url, onLoad, onProgress, onError ) { + }, + addLineGeometry: function ( vertices, uvs ) { - var scope = this; - var loader = new THREE.FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { + this.object.geometry.type = 'Line'; + const vLen = this.vertices.length; + const uvLen = this.uvs.length; - try { + for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) { - onLoad( scope.parse( text ) ); + this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); - } catch ( e ) { + } - if ( onError ) { + for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { - onError( e ); + this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); - } else { + } - console.error( e ); + } + }; + state.startObject( '', false ); + return state; - } + } // - scope.manager.itemError( url ); - } + class OBJLoader extends THREE.Loader { - }, onProgress, onError ); + constructor( manager ) { - }, - setMaterials: function ( materials ) { + super( manager ); + this.materials = null; - this.materials = materials; - return this; + } - }, - parse: function ( text ) { + load( url, onLoad, onProgress, onError ) { - var state = new ParserState(); + const scope = this; + const loader = new THREE.FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { - if ( text.indexOf( '\r\n' ) !== - 1 ) { + try { - // This is faster than String.split with regex that splits on both - text = text.replace( /\r\n/g, '\n' ); + onLoad( scope.parse( text ) ); - } + } catch ( e ) { + + if ( onError ) { - if ( text.indexOf( '\\\n' ) !== - 1 ) { + onError( e ); + + } else { - // join lines separated by a line continuation character (\) - text = text.replace( /\\\n/g, '' ); + console.error( e ); + + } + + scope.manager.itemError( url ); } - var lines = text.split( '\n' ); - var line = '', - lineFirstChar = ''; - var lineLength = 0; - var result = []; // Faster to just trim left side of the line. Use if available. + }, onProgress, onError ); - var trimLeft = typeof ''.trimLeft === 'function'; + } - for ( var i = 0, l = lines.length; i < l; i ++ ) { + setMaterials( materials ) { - line = lines[ i ]; - line = trimLeft ? line.trimLeft() : line.trim(); - lineLength = line.length; - if ( lineLength === 0 ) continue; - lineFirstChar = line.charAt( 0 ); // @todo invoke passed in handler if any + this.materials = materials; + return this; - if ( lineFirstChar === '#' ) continue; + } - if ( lineFirstChar === 'v' ) { + parse( text ) { - var data = line.split( /\s+/ ); + const state = new ParserState(); - switch ( data[ 0 ] ) { + if ( text.indexOf( '\r\n' ) !== - 1 ) { - case 'v': - state.vertices.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ), parseFloat( data[ 3 ] ) ); + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); - if ( data.length >= 7 ) { + } - state.colors.push( parseFloat( data[ 4 ] ), parseFloat( data[ 5 ] ), parseFloat( data[ 6 ] ) ); + if ( text.indexOf( '\\\n' ) !== - 1 ) { - } else { + // join lines separated by a line continuation character (\) + text = text.replace( /\\\n/g, '' ); - // if no colors are defined, add placeholders so color and vertex indices match - state.colors.push( undefined, undefined, undefined ); + } - } + const lines = text.split( '\n' ); + let line = '', + lineFirstChar = ''; + let lineLength = 0; + let result = []; // Faster to just trim left side of the line. Use if available. - break; + const trimLeft = typeof ''.trimLeft === 'function'; - case 'vn': - state.normals.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ), parseFloat( data[ 3 ] ) ); - break; + for ( let i = 0, l = lines.length; i < l; i ++ ) { - case 'vt': - state.uvs.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ) ); - break; + line = lines[ i ]; + line = trimLeft ? line.trimLeft() : line.trim(); + lineLength = line.length; + if ( lineLength === 0 ) continue; + lineFirstChar = line.charAt( 0 ); // @todo invoke passed in handler if any - } + if ( lineFirstChar === '#' ) continue; + + if ( lineFirstChar === 'v' ) { - } else if ( lineFirstChar === 'f' ) { + const data = line.split( /\s+/ ); - var lineData = line.substr( 1 ).trim(); - var vertexData = lineData.split( /\s+/ ); - var faceVertices = []; // Parse the face vertex data into an easy to work with format + switch ( data[ 0 ] ) { - for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { + case 'v': + state.vertices.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ), parseFloat( data[ 3 ] ) ); - var vertex = vertexData[ j ]; + if ( data.length >= 7 ) { - if ( vertex.length > 0 ) { + state.colors.push( parseFloat( data[ 4 ] ), parseFloat( data[ 5 ] ), parseFloat( data[ 6 ] ) ); - var vertexParts = vertex.split( '/' ); - faceVertices.push( vertexParts ); + } else { + + // if no colors are defined, add placeholders so color and vertex indices match + state.colors.push( undefined, undefined, undefined ); } - } // Draw an edge between the first vertex and all subsequent vertices to form an n-gon + break; + + case 'vn': + state.normals.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ), parseFloat( data[ 3 ] ) ); + break; + + case 'vt': + state.uvs.push( parseFloat( data[ 1 ] ), parseFloat( data[ 2 ] ) ); + break; + + } + + } else if ( lineFirstChar === 'f' ) { + const lineData = line.substr( 1 ).trim(); + const vertexData = lineData.split( /\s+/ ); + const faceVertices = []; // Parse the face vertex data into an easy to work with format - var v1 = faceVertices[ 0 ]; + for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) { - for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { + const vertex = vertexData[ j ]; - var v2 = faceVertices[ j ]; - var v3 = faceVertices[ j + 1 ]; - state.addFace( v1[ 0 ], v2[ 0 ], v3[ 0 ], v1[ 1 ], v2[ 1 ], v3[ 1 ], v1[ 2 ], v2[ 2 ], v3[ 2 ] ); + if ( vertex.length > 0 ) { + + const vertexParts = vertex.split( '/' ); + faceVertices.push( vertexParts ); } - } else if ( lineFirstChar === 'l' ) { + } // Draw an edge between the first vertex and all subsequent vertices to form an n-gon - var lineParts = line.substring( 1 ).trim().split( ' ' ); - var lineVertices = [], - lineUVs = []; - if ( line.indexOf( '/' ) === - 1 ) { + const v1 = faceVertices[ 0 ]; - lineVertices = lineParts; + for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { - } else { + const v2 = faceVertices[ j ]; + const v3 = faceVertices[ j + 1 ]; + state.addFace( v1[ 0 ], v2[ 0 ], v3[ 0 ], v1[ 1 ], v2[ 1 ], v3[ 1 ], v1[ 2 ], v2[ 2 ], v3[ 2 ] ); - for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { + } - var parts = lineParts[ li ].split( '/' ); - if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] ); - if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] ); + } else if ( lineFirstChar === 'l' ) { - } + const lineParts = line.substring( 1 ).trim().split( ' ' ); + let lineVertices = []; + const lineUVs = []; + + if ( line.indexOf( '/' ) === - 1 ) { + + lineVertices = lineParts; + + } else { + + for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) { + + const parts = lineParts[ li ].split( '/' ); + if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] ); + if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] ); } - state.addLineGeometry( lineVertices, lineUVs ); + } + + state.addLineGeometry( lineVertices, lineUVs ); - } else if ( lineFirstChar === 'p' ) { + } else if ( lineFirstChar === 'p' ) { - var lineData = line.substr( 1 ).trim(); - var pointData = lineData.split( ' ' ); - state.addPointGeometry( pointData ); + const lineData = line.substr( 1 ).trim(); + const pointData = lineData.split( ' ' ); + state.addPointGeometry( pointData ); - } else if ( ( result = object_pattern.exec( line ) ) !== null ) { + } else if ( ( result = _object_pattern.exec( line ) ) !== null ) { - // o object_name - // or - // g group_name - // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 - // var name = result[ 0 ].substr( 1 ).trim(); - var name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); - state.startObject( name ); + // o object_name + // or + // g group_name + // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 + // let name = result[ 0 ].substr( 1 ).trim(); + const name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); + state.startObject( name ); - } else if ( material_use_pattern.test( line ) ) { + } else if ( _material_use_pattern.test( line ) ) { - // material - state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); + // material + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); - } else if ( material_library_pattern.test( line ) ) { + } else if ( _material_library_pattern.test( line ) ) { - // mtl file - state.materialLibraries.push( line.substring( 7 ).trim() ); + // mtl file + state.materialLibraries.push( line.substring( 7 ).trim() ); - } else if ( map_use_pattern.test( line ) ) { + } else if ( _map_use_pattern.test( line ) ) { - // the line is parsed but ignored since the loader assumes textures are defined MTL files - // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) - console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' ); + // the line is parsed but ignored since the loader assumes textures are defined MTL files + // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) + console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' ); - } else if ( lineFirstChar === 's' ) { + } else if ( lineFirstChar === 's' ) { - result = line.split( ' ' ); // smooth shading - // @todo Handle files that have varying smooth values for a set of faces inside one geometry, - // but does not define a usemtl for each face set. - // This should be detected and a dummy material created (later MultiMaterial and geometry groups). - // This requires some care to not create extra material on each smooth value for "normal" obj files. - // where explicit usemtl defines geometry groups. - // Example asset: examples/models/obj/cerberus/Cerberus.obj + result = line.split( ' ' ); // smooth shading + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj - /* + /* * http://paulbourke.net/dataformats/obj/ * or * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf @@ -581,217 +593,215 @@ * than 0." */ - if ( result.length > 1 ) { + if ( result.length > 1 ) { - var value = result[ 1 ].trim().toLowerCase(); - state.object.smooth = value !== '0' && value !== 'off'; - - } else { + const value = result[ 1 ].trim().toLowerCase(); + state.object.smooth = value !== '0' && value !== 'off'; - // ZBrush can produce "s" lines #11707 - state.object.smooth = true; + } else { - } + // ZBrush can produce "s" lines #11707 + state.object.smooth = true; - var material = state.object.currentMaterial(); - if ( material ) material.smooth = state.object.smooth; + } - } else { + const material = state.object.currentMaterial(); + if ( material ) material.smooth = state.object.smooth; - // Handle null terminated files without exception - if ( line === '\0' ) continue; - console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); + } else { - } + // Handle null terminated files without exception + if ( line === '\0' ) continue; + console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); } - state.finalize(); - var container = new THREE.Group(); - container.materialLibraries = [].concat( state.materialLibraries ); - var hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 ); + } - if ( hasPrimitives === true ) { + state.finalize(); + const container = new THREE.Group(); + container.materialLibraries = [].concat( state.materialLibraries ); + const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 ); - for ( var i = 0, l = state.objects.length; i < l; i ++ ) { + if ( hasPrimitives === true ) { - var object = state.objects[ i ]; - var geometry = object.geometry; - var materials = object.materials; - var isLine = geometry.type === 'Line'; - var isPoints = geometry.type === 'Points'; - var hasVertexColors = false; // Skip o/g line declarations that did not follow with any faces + for ( let i = 0, l = state.objects.length; i < l; i ++ ) { - if ( geometry.vertices.length === 0 ) continue; - var buffergeometry = new THREE.BufferGeometry(); - buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); + const object = state.objects[ i ]; + const geometry = object.geometry; + const materials = object.materials; + const isLine = geometry.type === 'Line'; + const isPoints = geometry.type === 'Points'; + let hasVertexColors = false; // Skip o/g line declarations that did not follow with any faces - if ( geometry.normals.length > 0 ) { + if ( geometry.vertices.length === 0 ) continue; + const buffergeometry = new THREE.BufferGeometry(); + buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); - buffergeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); + if ( geometry.normals.length > 0 ) { - } + buffergeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); - if ( geometry.colors.length > 0 ) { + } - hasVertexColors = true; - buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) ); + if ( geometry.colors.length > 0 ) { - } + hasVertexColors = true; + buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) ); - if ( geometry.hasUVIndices === true ) { + } - buffergeometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) ); + if ( geometry.hasUVIndices === true ) { - } // Create materials + buffergeometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) ); + } // Create materials - var createdMaterials = []; - for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + const createdMaterials = []; - var sourceMaterial = materials[ mi ]; - var materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; - var material = state.materials[ materialHash ]; + for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { - if ( this.materials !== null ) { + const sourceMaterial = materials[ mi ]; + const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; + let material = state.materials[ materialHash ]; - material = this.materials.create( sourceMaterial.name ); // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( this.materials !== null ) { - if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { + material = this.materials.create( sourceMaterial.name ); // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. - var materialLine = new THREE.LineBasicMaterial(); - THREE.Material.prototype.copy.call( materialLine, material ); - materialLine.color.copy( material.color ); - material = materialLine; + if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { - } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) { + const materialLine = new THREE.LineBasicMaterial(); + THREE.Material.prototype.copy.call( materialLine, material ); + materialLine.color.copy( material.color ); + material = materialLine; - var materialPoints = new THREE.PointsMaterial( { - size: 10, - sizeAttenuation: false - } ); - THREE.Material.prototype.copy.call( materialPoints, material ); - materialPoints.color.copy( material.color ); - materialPoints.map = material.map; - material = materialPoints; + } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) { - } + const materialPoints = new THREE.PointsMaterial( { + size: 10, + sizeAttenuation: false + } ); + THREE.Material.prototype.copy.call( materialPoints, material ); + materialPoints.color.copy( material.color ); + materialPoints.map = material.map; + material = materialPoints; } - if ( material === undefined ) { - - if ( isLine ) { + } - material = new THREE.LineBasicMaterial(); + if ( material === undefined ) { - } else if ( isPoints ) { + if ( isLine ) { - material = new THREE.PointsMaterial( { - size: 1, - sizeAttenuation: false - } ); + material = new THREE.LineBasicMaterial(); - } else { + } else if ( isPoints ) { - material = new THREE.MeshPhongMaterial(); + material = new THREE.PointsMaterial( { + size: 1, + sizeAttenuation: false + } ); - } + } else { - material.name = sourceMaterial.name; - material.flatShading = sourceMaterial.smooth ? false : true; - material.vertexColors = hasVertexColors; - state.materials[ materialHash ] = material; + material = new THREE.MeshPhongMaterial(); } - createdMaterials.push( material ); - - } // Create mesh + material.name = sourceMaterial.name; + material.flatShading = sourceMaterial.smooth ? false : true; + material.vertexColors = hasVertexColors; + state.materials[ materialHash ] = material; + } - var mesh; + createdMaterials.push( material ); - if ( createdMaterials.length > 1 ) { + } // Create mesh - for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { - var sourceMaterial = materials[ mi ]; - buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + let mesh; - } + if ( createdMaterials.length > 1 ) { - if ( isLine ) { + for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { - mesh = new THREE.LineSegments( buffergeometry, createdMaterials ); + const sourceMaterial = materials[ mi ]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); - } else if ( isPoints ) { + } - mesh = new THREE.Points( buffergeometry, createdMaterials ); + if ( isLine ) { - } else { + mesh = new THREE.LineSegments( buffergeometry, createdMaterials ); - mesh = new THREE.Mesh( buffergeometry, createdMaterials ); + } else if ( isPoints ) { - } + mesh = new THREE.Points( buffergeometry, createdMaterials ); } else { - if ( isLine ) { + mesh = new THREE.Mesh( buffergeometry, createdMaterials ); - mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ); + } - } else if ( isPoints ) { + } else { - mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] ); + if ( isLine ) { - } else { + mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ); - mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ); + } else if ( isPoints ) { - } + mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] ); - } + } else { - mesh.name = object.name; - container.add( mesh ); + mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ); + + } } - } else { + mesh.name = object.name; + container.add( mesh ); - // if there is only the default parser state object with no geometry data, interpret data as point cloud - if ( state.vertices.length > 0 ) { + } - var material = new THREE.PointsMaterial( { - size: 1, - sizeAttenuation: false - } ); - var buffergeometry = new THREE.BufferGeometry(); - buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( state.vertices, 3 ) ); + } else { - if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) { + // if there is only the default parser state object with no geometry data, interpret data as point cloud + if ( state.vertices.length > 0 ) { - buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( state.colors, 3 ) ); - material.vertexColors = true; + const material = new THREE.PointsMaterial( { + size: 1, + sizeAttenuation: false + } ); + const buffergeometry = new THREE.BufferGeometry(); + buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( state.vertices, 3 ) ); - } + if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) { - var points = new THREE.Points( buffergeometry, material ); - container.add( points ); + buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( state.colors, 3 ) ); + material.vertexColors = true; } - } + const points = new THREE.Points( buffergeometry, material ); + container.add( points ); - return container; + } } - } ); - return OBJLoader; - }(); + return container; + + } + + } THREE.OBJLoader = OBJLoader; diff --git a/examples/js/loaders/PRWMLoader.js b/examples/js/loaders/PRWMLoader.js index 561ed35067ba1f..e2e31965c0aca3 100644 --- a/examples/js/loaders/PRWMLoader.js +++ b/examples/js/loaders/PRWMLoader.js @@ -4,272 +4,256 @@ * See https://github.com/kchapelier/PRWM for more informations about this file format */ - var PRWMLoader = function () { - - var bigEndianPlatform = null; - /** + let bigEndianPlatform = null; + /** * Check if the endianness of the platform is big-endian (most significant bit first) * @returns {boolean} True if big-endian, false if little-endian */ - function isBigEndianPlatform() { + function isBigEndianPlatform() { - if ( bigEndianPlatform === null ) { + if ( bigEndianPlatform === null ) { - var buffer = new ArrayBuffer( 2 ), - uint8Array = new Uint8Array( buffer ), - uint16Array = new Uint16Array( buffer ); - uint8Array[ 0 ] = 0xAA; // set first byte + const buffer = new ArrayBuffer( 2 ), + uint8Array = new Uint8Array( buffer ), + uint16Array = new Uint16Array( buffer ); + uint8Array[ 0 ] = 0xAA; // set first byte - uint8Array[ 1 ] = 0xBB; // set second byte + uint8Array[ 1 ] = 0xBB; // set second byte - bigEndianPlatform = uint16Array[ 0 ] === 0xAABB; + bigEndianPlatform = uint16Array[ 0 ] === 0xAABB; - } + } - return bigEndianPlatform; + return bigEndianPlatform; - } // match the values defined in the spec to the TypedArray types + } // match the values defined in the spec to the TypedArray types - var InvertedEncodingTypes = [ null, Float32Array, null, Int8Array, Int16Array, null, Int32Array, Uint8Array, Uint16Array, null, Uint32Array ]; // define the method to use on a DataView, corresponding the TypedArray type + const InvertedEncodingTypes = [ null, Float32Array, null, Int8Array, Int16Array, null, Int32Array, Uint8Array, Uint16Array, null, Uint32Array ]; // define the method to use on a DataView, corresponding the TypedArray type - var getMethods = { - Uint16Array: 'getUint16', - Uint32Array: 'getUint32', - Int16Array: 'getInt16', - Int32Array: 'getInt32', - Float32Array: 'getFloat32', - Float64Array: 'getFloat64' - }; + const getMethods = { + Uint16Array: 'getUint16', + Uint32Array: 'getUint32', + Int16Array: 'getInt16', + Int32Array: 'getInt32', + Float32Array: 'getFloat32', + Float64Array: 'getFloat64' + }; - function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) { + function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) { - var bytesPerElement = viewType.BYTES_PER_ELEMENT, - result; + const bytesPerElement = viewType.BYTES_PER_ELEMENT; + let result; - if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) { + if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) { - result = new viewType( sourceArrayBuffer, position, length ); + result = new viewType( sourceArrayBuffer, position, length ); - } else { + } else { - var readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ), - getMethod = getMethods[ viewType.name ], - littleEndian = ! fromBigEndian, - i = 0; - result = new viewType( length ); + const readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ), + getMethod = getMethods[ viewType.name ], + littleEndian = ! fromBigEndian; + result = new viewType( length ); - for ( ; i < length; i ++ ) { + for ( let i = 0; i < length; i ++ ) { - result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian ); - - } + result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian ); } - return result; - } - function decodePrwm( buffer ) { + return result; - var array = new Uint8Array( buffer ), - version = array[ 0 ], - flags = array[ 1 ], - indexedGeometry = !! ( flags >> 7 & 0x01 ), - indicesType = flags >> 6 & 0x01, - bigEndian = ( flags >> 5 & 0x01 ) === 1, - attributesNumber = flags & 0x1F, - valuesNumber = 0, - indicesNumber = 0; + } - if ( bigEndian ) { + function decodePrwm( buffer ) { - valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ]; - indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ]; + const array = new Uint8Array( buffer ), + version = array[ 0 ]; + let flags = array[ 1 ]; + const indexedGeometry = !! ( flags >> 7 & 0x01 ), + indicesType = flags >> 6 & 0x01, + bigEndian = ( flags >> 5 & 0x01 ) === 1, + attributesNumber = flags & 0x1F; + let valuesNumber = 0, + indicesNumber = 0; - } else { + if ( bigEndian ) { - valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 ); - indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 ); + valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ]; + indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ]; - } - /** PRELIMINARY CHECKS **/ + } else { + valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 ); + indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 ); - if ( version === 0 ) { + } + /** PRELIMINARY CHECKS **/ - throw new Error( 'PRWM decoder: Invalid format version: 0' ); - } else if ( version !== 1 ) { + if ( version === 0 ) { - throw new Error( 'PRWM decoder: Unsupported format version: ' + version ); + throw new Error( 'PRWM decoder: Invalid format version: 0' ); - } + } else if ( version !== 1 ) { - if ( ! indexedGeometry ) { + throw new Error( 'PRWM decoder: Unsupported format version: ' + version ); - if ( indicesType !== 0 ) { + } - throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' ); + if ( ! indexedGeometry ) { - } else if ( indicesNumber !== 0 ) { + if ( indicesType !== 0 ) { - throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' ); + throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' ); - } + } else if ( indicesNumber !== 0 ) { + + throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' ); } - /** PARSING **/ + } + /** PARSING **/ - var pos = 8; - var attributes = {}, - attributeName, - char, - attributeType, - cardinality, - encodingType, - arrayType, - values, - indices, - i; - for ( i = 0; i < attributesNumber; i ++ ) { + let pos = 8; + const attributes = {}; - attributeName = ''; + for ( let i = 0; i < attributesNumber; i ++ ) { - while ( pos < array.length ) { + let attributeName = ''; - char = array[ pos ]; - pos ++; + while ( pos < array.length ) { - if ( char === 0 ) { + const char = array[ pos ]; + pos ++; - break; + if ( char === 0 ) { - } else { + break; - attributeName += String.fromCharCode( char ); + } else { - } + attributeName += String.fromCharCode( char ); } - flags = array[ pos ]; - attributeType = flags >> 7 & 0x01; - cardinality = ( flags >> 4 & 0x03 ) + 1; - encodingType = flags & 0x0F; - arrayType = InvertedEncodingTypes[ encodingType ]; - pos ++; // padding to next multiple of 4 - - pos = Math.ceil( pos / 4 ) * 4; - values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian ); - pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber; - attributes[ attributeName ] = { - type: attributeType, - cardinality: cardinality, - values: values - }; - } + flags = array[ pos ]; + const attributeType = flags >> 7 & 0x01; + const cardinality = ( flags >> 4 & 0x03 ) + 1; + const encodingType = flags & 0x0F; + const arrayType = InvertedEncodingTypes[ encodingType ]; + pos ++; // padding to next multiple of 4 + pos = Math.ceil( pos / 4 ) * 4; - indices = null; + const values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian ); + pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber; + attributes[ attributeName ] = { + type: attributeType, + cardinality: cardinality, + values: values + }; - if ( indexedGeometry ) { + } - indices = copyFromBuffer( buffer, indicesType === 1 ? Uint32Array : Uint16Array, pos, indicesNumber, bigEndian ); + pos = Math.ceil( pos / 4 ) * 4; + let indices = null; - } + if ( indexedGeometry ) { - return { - version: version, - attributes: attributes, - indices: indices - }; + indices = copyFromBuffer( buffer, indicesType === 1 ? Uint32Array : Uint16Array, pos, indicesNumber, bigEndian ); - } // Define the public interface + } + return { + version: version, + attributes: attributes, + indices: indices + }; - function PRWMLoader( manager ) { + } // Define the public interface - THREE.Loader.call( this, manager ); - } + class PRWMLoader extends THREE.Loader { - PRWMLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { - constructor: PRWMLoader, - load: function ( url, onLoad, onProgress, onError ) { + constructor( manager ) { - var scope = this; - var loader = new THREE.FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' ); - loader.load( url, function ( arrayBuffer ) { + super( manager ); - try { + } + + load( url, onLoad, onProgress, onError ) { - onLoad( scope.parse( arrayBuffer ) ); + const scope = this; + const loader = new THREE.FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' ); + loader.load( url, function ( arrayBuffer ) { - } catch ( e ) { + try { - if ( onError ) { + onLoad( scope.parse( arrayBuffer ) ); - onError( e ); + } catch ( e ) { - } else { + if ( onError ) { - console.error( e ); + onError( e ); - } + } else { - scope.manager.itemError( url ); + console.error( e ); } - }, onProgress, onError ); + scope.manager.itemError( url ); + + } - }, - parse: function ( arrayBuffer ) { + }, onProgress, onError ); - var data = decodePrwm( arrayBuffer ), - attributesKey = Object.keys( data.attributes ), - bufferGeometry = new THREE.BufferGeometry(), - attribute, - i; + } - for ( i = 0; i < attributesKey.length; i ++ ) { + parse( arrayBuffer ) { - attribute = data.attributes[ attributesKey[ i ] ]; - bufferGeometry.setAttribute( attributesKey[ i ], new THREE.BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) ); + const data = decodePrwm( arrayBuffer ), + attributesKey = Object.keys( data.attributes ), + bufferGeometry = new THREE.BufferGeometry(); - } + for ( let i = 0; i < attributesKey.length; i ++ ) { - if ( data.indices !== null ) { + const attribute = data.attributes[ attributesKey[ i ] ]; + bufferGeometry.setAttribute( attributesKey[ i ], new THREE.BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) ); - bufferGeometry.setIndex( new THREE.BufferAttribute( data.indices, 1 ) ); + } - } + if ( data.indices !== null ) { - return bufferGeometry; + bufferGeometry.setIndex( new THREE.BufferAttribute( data.indices, 1 ) ); } - } ); - PRWMLoader.isBigEndianPlatform = function () { + return bufferGeometry; - return isBigEndianPlatform(); + } - }; + static isBigEndianPlatform() { - return PRWMLoader; + return isBigEndianPlatform(); + + } - }(); + } THREE.PRWMLoader = PRWMLoader; diff --git a/examples/js/loaders/SVGLoader.js b/examples/js/loaders/SVGLoader.js index 04f9bf1478fc00..3bdc26427cb475 100644 --- a/examples/js/loaders/SVGLoader.js +++ b/examples/js/loaders/SVGLoader.js @@ -1,21 +1,21 @@ ( function () { - var SVGLoader = function ( manager ) { + class SVGLoader extends THREE.Loader { - THREE.Loader.call( this, manager ); // Default dots per inch + constructor( manager ) { - this.defaultDPI = 90; // Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px' + super( manager ); // Default dots per inch - this.defaultUnit = 'px'; + this.defaultDPI = 90; // Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px' - }; + this.defaultUnit = 'px'; - SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { - constructor: SVGLoader, - load: function ( url, onLoad, onProgress, onError ) { + } + + load( url, onLoad, onProgress, onError ) { - var scope = this; - var loader = new THREE.FileLoader( scope.manager ); + const scope = this; + const loader = new THREE.FileLoader( scope.manager ); loader.setPath( scope.path ); loader.setRequestHeader( scope.requestHeader ); loader.setWithCredentials( scope.withCredentials ); @@ -43,17 +43,18 @@ }, onProgress, onError ); - }, - parse: function ( text ) { + } + + parse( text ) { - var scope = this; + const scope = this; function parseNode( node, style ) { if ( node.nodeType !== 1 ) return; - var transform = getNodeTransform( node ); - var traverseChildNodes = true; - var path = null; + const transform = getNodeTransform( node ); + let traverseChildNodes = true; + let path = null; switch ( node.nodeName ) { @@ -109,8 +110,8 @@ case 'use': style = parseStyle( node, style ); - var usedNodeId = node.href.baseVal.substring( 1 ); - var usedNode = node.viewportElement.getElementById( usedNodeId ); + const usedNodeId = node.href.baseVal.substring( 1 ); + const usedNode = node.viewportElement.getElementById( usedNodeId ); if ( usedNode ) { @@ -147,9 +148,9 @@ if ( traverseChildNodes ) { - var nodes = node.childNodes; + const nodes = node.childNodes; - for ( var i = 0; i < nodes.length; i ++ ) { + for ( let i = 0; i < nodes.length; i ++ ) { parseNode( nodes[ i ], style ); @@ -177,21 +178,21 @@ function parsePathNode( node ) { - var path = new THREE.ShapePath(); - var point = new THREE.Vector2(); - var control = new THREE.Vector2(); - var firstPoint = new THREE.Vector2(); - var isFirstPoint = true; - var doSetFirstPoint = false; - var d = node.getAttribute( 'd' ); // console.log( d ); + const path = new THREE.ShapePath(); + const point = new THREE.Vector2(); + const control = new THREE.Vector2(); + const firstPoint = new THREE.Vector2(); + let isFirstPoint = true; + let doSetFirstPoint = false; + const d = node.getAttribute( 'd' ); // console.log( d ); - var commands = d.match( /[a-df-z][^a-df-z]*/ig ); + const commands = d.match( /[a-df-z][^a-df-z]*/ig ); - for ( var i = 0, l = commands.length; i < l; i ++ ) { + for ( let i = 0, l = commands.length; i < l; i ++ ) { - var command = commands[ i ]; - var type = command.charAt( 0 ); - var data = command.substr( 1 ).trim(); + const command = commands[ i ]; + const type = command.charAt( 0 ); + const data = command.substr( 1 ).trim(); if ( isFirstPoint === true ) { @@ -200,12 +201,14 @@ } + let numbers; + switch ( type ) { case 'M': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { point.x = numbers[ j + 0 ]; point.y = numbers[ j + 1 ]; @@ -229,9 +232,9 @@ break; case 'H': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j ++ ) { + for ( let j = 0, jl = numbers.length; j < jl; j ++ ) { point.x = numbers[ j ]; control.x = point.x; @@ -244,9 +247,9 @@ break; case 'V': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j ++ ) { + for ( let j = 0, jl = numbers.length; j < jl; j ++ ) { point.y = numbers[ j ]; control.x = point.x; @@ -259,9 +262,9 @@ break; case 'L': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { point.x = numbers[ j + 0 ]; point.y = numbers[ j + 1 ]; @@ -275,9 +278,9 @@ break; case 'C': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 6 ) { path.bezierCurveTo( numbers[ j + 0 ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ], numbers[ j + 4 ], numbers[ j + 5 ] ); control.x = numbers[ j + 2 ]; @@ -291,9 +294,9 @@ break; case 'S': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 4 ) { path.bezierCurveTo( getReflection( point.x, control.x ), getReflection( point.y, control.y ), numbers[ j + 0 ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ] ); control.x = numbers[ j + 0 ]; @@ -307,9 +310,9 @@ break; case 'Q': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 4 ) { path.quadraticCurveTo( numbers[ j + 0 ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ] ); control.x = numbers[ j + 0 ]; @@ -323,12 +326,12 @@ break; case 'T': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { - var rx = getReflection( point.x, control.x ); - var ry = getReflection( point.y, control.y ); + const rx = getReflection( point.x, control.x ); + const ry = getReflection( point.y, control.y ); path.quadraticCurveTo( rx, ry, numbers[ j + 0 ], numbers[ j + 1 ] ); control.x = rx; control.y = ry; @@ -341,13 +344,13 @@ break; case 'A': - var numbers = parseFloats( data, [ 3, 4 ], 7 ); + numbers = parseFloats( data, [ 3, 4 ], 7 ); - for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 7 ) { // skip command if start point == end point if ( numbers[ j + 5 ] == point.x && numbers[ j + 6 ] == point.y ) continue; - var start = point.clone(); + const start = point.clone(); point.x = numbers[ j + 5 ]; point.y = numbers[ j + 6 ]; control.x = point.x; @@ -360,9 +363,9 @@ break; case 'm': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { point.x += numbers[ j + 0 ]; point.y += numbers[ j + 1 ]; @@ -386,9 +389,9 @@ break; case 'h': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j ++ ) { + for ( let j = 0, jl = numbers.length; j < jl; j ++ ) { point.x += numbers[ j ]; control.x = point.x; @@ -401,9 +404,9 @@ break; case 'v': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j ++ ) { + for ( let j = 0, jl = numbers.length; j < jl; j ++ ) { point.y += numbers[ j ]; control.x = point.x; @@ -416,9 +419,9 @@ break; case 'l': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { point.x += numbers[ j + 0 ]; point.y += numbers[ j + 1 ]; @@ -432,9 +435,9 @@ break; case 'c': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 6 ) { path.bezierCurveTo( point.x + numbers[ j + 0 ], point.y + numbers[ j + 1 ], point.x + numbers[ j + 2 ], point.y + numbers[ j + 3 ], point.x + numbers[ j + 4 ], point.y + numbers[ j + 5 ] ); control.x = point.x + numbers[ j + 2 ]; @@ -448,9 +451,9 @@ break; case 's': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 4 ) { path.bezierCurveTo( getReflection( point.x, control.x ), getReflection( point.y, control.y ), point.x + numbers[ j + 0 ], point.y + numbers[ j + 1 ], point.x + numbers[ j + 2 ], point.y + numbers[ j + 3 ] ); control.x = point.x + numbers[ j + 0 ]; @@ -464,9 +467,9 @@ break; case 'q': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 4 ) { path.quadraticCurveTo( point.x + numbers[ j + 0 ], point.y + numbers[ j + 1 ], point.x + numbers[ j + 2 ], point.y + numbers[ j + 3 ] ); control.x = point.x + numbers[ j + 0 ]; @@ -480,12 +483,12 @@ break; case 't': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { - var rx = getReflection( point.x, control.x ); - var ry = getReflection( point.y, control.y ); + const rx = getReflection( point.x, control.x ); + const ry = getReflection( point.y, control.y ); path.quadraticCurveTo( rx, ry, point.x + numbers[ j + 0 ], point.y + numbers[ j + 1 ] ); control.x = rx; control.y = ry; @@ -498,13 +501,13 @@ break; case 'a': - var numbers = parseFloats( data, [ 3, 4 ], 7 ); + numbers = parseFloats( data, [ 3, 4 ], 7 ); - for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 7 ) { // skip command if no displacement if ( numbers[ j + 5 ] == 0 && numbers[ j + 6 ] == 0 ) continue; - var start = point.clone(); + const start = point.clone(); point.x += numbers[ j + 5 ]; point.y += numbers[ j + 6 ]; control.x = point.x; @@ -549,13 +552,13 @@ if ( ! node.sheet || ! node.sheet.cssRules || ! node.sheet.cssRules.length ) return; - for ( var i = 0; i < node.sheet.cssRules.length; i ++ ) { + for ( let i = 0; i < node.sheet.cssRules.length; i ++ ) { - var stylesheet = node.sheet.cssRules[ i ]; + const stylesheet = node.sheet.cssRules[ i ]; if ( stylesheet.type !== 1 ) continue; - var selectorList = stylesheet.selectorText.split( /,/gm ).filter( Boolean ).map( i => i.trim() ); + const selectorList = stylesheet.selectorText.split( /,/gm ).filter( Boolean ).map( i => i.trim() ); - for ( var j = 0; j < selectorList.length; j ++ ) { + for ( let j = 0; j < selectorList.length; j ++ ) { stylesheets[ selectorList[ j ] ] = Object.assign( stylesheets[ selectorList[ j ] ] || {}, stylesheet.style ); @@ -589,22 +592,22 @@ rx = Math.abs( rx ); ry = Math.abs( ry ); // Compute (x1', y1') - var dx2 = ( start.x - end.x ) / 2.0; - var dy2 = ( start.y - end.y ) / 2.0; - var x1p = Math.cos( x_axis_rotation ) * dx2 + Math.sin( x_axis_rotation ) * dy2; - var y1p = - Math.sin( x_axis_rotation ) * dx2 + Math.cos( x_axis_rotation ) * dy2; // Compute (cx', cy') + const dx2 = ( start.x - end.x ) / 2.0; + const dy2 = ( start.y - end.y ) / 2.0; + const x1p = Math.cos( x_axis_rotation ) * dx2 + Math.sin( x_axis_rotation ) * dy2; + const y1p = - Math.sin( x_axis_rotation ) * dx2 + Math.cos( x_axis_rotation ) * dy2; // Compute (cx', cy') - var rxs = rx * rx; - var rys = ry * ry; - var x1ps = x1p * x1p; - var y1ps = y1p * y1p; // Ensure radii are large enough + let rxs = rx * rx; + let rys = ry * ry; + const x1ps = x1p * x1p; + const y1ps = y1p * y1p; // Ensure radii are large enough - var cr = x1ps / rxs + y1ps / rys; + const cr = x1ps / rxs + y1ps / rys; if ( cr > 1 ) { // scale up rx,ry equally so cr == 1 - var s = Math.sqrt( cr ); + const s = Math.sqrt( cr ); rx = s * rx; ry = s * ry; rxs = rx * rx; @@ -612,27 +615,27 @@ } - var dq = rxs * y1ps + rys * x1ps; - var pq = ( rxs * rys - dq ) / dq; - var q = Math.sqrt( Math.max( 0, pq ) ); + const dq = rxs * y1ps + rys * x1ps; + const pq = ( rxs * rys - dq ) / dq; + let q = Math.sqrt( Math.max( 0, pq ) ); if ( large_arc_flag === sweep_flag ) q = - q; - var cxp = q * rx * y1p / ry; - var cyp = - q * ry * x1p / rx; // Step 3: Compute (cx, cy) from (cx', cy') + const cxp = q * rx * y1p / ry; + const cyp = - q * ry * x1p / rx; // Step 3: Compute (cx, cy) from (cx', cy') - var cx = Math.cos( x_axis_rotation ) * cxp - Math.sin( x_axis_rotation ) * cyp + ( start.x + end.x ) / 2; - var cy = Math.sin( x_axis_rotation ) * cxp + Math.cos( x_axis_rotation ) * cyp + ( start.y + end.y ) / 2; // Step 4: Compute θ1 and Δθ + const cx = Math.cos( x_axis_rotation ) * cxp - Math.sin( x_axis_rotation ) * cyp + ( start.x + end.x ) / 2; + const cy = Math.sin( x_axis_rotation ) * cxp + Math.cos( x_axis_rotation ) * cyp + ( start.y + end.y ) / 2; // Step 4: Compute θ1 and Δθ - var theta = svgAngle( 1, 0, ( x1p - cxp ) / rx, ( y1p - cyp ) / ry ); - var delta = svgAngle( ( x1p - cxp ) / rx, ( y1p - cyp ) / ry, ( - x1p - cxp ) / rx, ( - y1p - cyp ) / ry ) % ( Math.PI * 2 ); + const theta = svgAngle( 1, 0, ( x1p - cxp ) / rx, ( y1p - cyp ) / ry ); + const delta = svgAngle( ( x1p - cxp ) / rx, ( y1p - cyp ) / ry, ( - x1p - cxp ) / rx, ( - y1p - cyp ) / ry ) % ( Math.PI * 2 ); path.currentPath.absellipse( cx, cy, rx, ry, theta, theta + delta, sweep_flag === 0, x_axis_rotation ); } function svgAngle( ux, uy, vx, vy ) { - var dot = ux * vx + uy * vy; - var len = Math.sqrt( ux * ux + uy * uy ) * Math.sqrt( vx * vx + vy * vy ); - var ang = Math.acos( Math.max( - 1, Math.min( 1, dot / len ) ) ); // floating point precision, slightly over values appear + const dot = ux * vx + uy * vy; + const len = Math.sqrt( ux * ux + uy * uy ) * Math.sqrt( vx * vx + vy * vy ); + let ang = Math.acos( Math.max( - 1, Math.min( 1, dot / len ) ) ); // floating point precision, slightly over values appear if ( ux * vy - uy * vx < 0 ) ang = - ang; return ang; @@ -646,13 +649,13 @@ function parseRectNode( node ) { - var x = parseFloatWithUnits( node.getAttribute( 'x' ) || 0 ); - var y = parseFloatWithUnits( node.getAttribute( 'y' ) || 0 ); - var rx = parseFloatWithUnits( node.getAttribute( 'rx' ) || 0 ); - var ry = parseFloatWithUnits( node.getAttribute( 'ry' ) || 0 ); - var w = parseFloatWithUnits( node.getAttribute( 'width' ) ); - var h = parseFloatWithUnits( node.getAttribute( 'height' ) ); - var path = new THREE.ShapePath(); + const x = parseFloatWithUnits( node.getAttribute( 'x' ) || 0 ); + const y = parseFloatWithUnits( node.getAttribute( 'y' ) || 0 ); + const rx = parseFloatWithUnits( node.getAttribute( 'rx' ) || 0 ); + const ry = parseFloatWithUnits( node.getAttribute( 'ry' ) || 0 ); + const w = parseFloatWithUnits( node.getAttribute( 'width' ) ); + const h = parseFloatWithUnits( node.getAttribute( 'height' ) ); + const path = new THREE.ShapePath(); path.moveTo( x + 2 * rx, y ); path.lineTo( x + w - 2 * rx, y ); if ( rx !== 0 || ry !== 0 ) path.bezierCurveTo( x + w, y, x + w, y, x + w, y + 2 * ry ); @@ -682,8 +685,8 @@ function iterator( match, a, b ) { - var x = parseFloatWithUnits( a ); - var y = parseFloatWithUnits( b ); + const x = parseFloatWithUnits( a ); + const y = parseFloatWithUnits( b ); if ( index === 0 ) { @@ -699,9 +702,9 @@ } - var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; - var path = new THREE.ShapePath(); - var index = 0; + const regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; + const path = new THREE.ShapePath(); + let index = 0; node.getAttribute( 'points' ).replace( regex, iterator ); path.currentPath.autoClose = true; return path; @@ -712,8 +715,8 @@ function iterator( match, a, b ) { - var x = parseFloatWithUnits( a ); - var y = parseFloatWithUnits( b ); + const x = parseFloatWithUnits( a ); + const y = parseFloatWithUnits( b ); if ( index === 0 ) { @@ -729,9 +732,9 @@ } - var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; - var path = new THREE.ShapePath(); - var index = 0; + const regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; + const path = new THREE.ShapePath(); + let index = 0; node.getAttribute( 'points' ).replace( regex, iterator ); path.currentPath.autoClose = false; return path; @@ -740,12 +743,12 @@ function parseCircleNode( node ) { - var x = parseFloatWithUnits( node.getAttribute( 'cx' ) || 0 ); - var y = parseFloatWithUnits( node.getAttribute( 'cy' ) || 0 ); - var r = parseFloatWithUnits( node.getAttribute( 'r' ) || 0 ); - var subpath = new THREE.Path(); + const x = parseFloatWithUnits( node.getAttribute( 'cx' ) || 0 ); + const y = parseFloatWithUnits( node.getAttribute( 'cy' ) || 0 ); + const r = parseFloatWithUnits( node.getAttribute( 'r' ) || 0 ); + const subpath = new THREE.Path(); subpath.absarc( x, y, r, 0, Math.PI * 2 ); - var path = new THREE.ShapePath(); + const path = new THREE.ShapePath(); path.subPaths.push( subpath ); return path; @@ -753,13 +756,13 @@ function parseEllipseNode( node ) { - var x = parseFloatWithUnits( node.getAttribute( 'cx' ) || 0 ); - var y = parseFloatWithUnits( node.getAttribute( 'cy' ) || 0 ); - var rx = parseFloatWithUnits( node.getAttribute( 'rx' ) || 0 ); - var ry = parseFloatWithUnits( node.getAttribute( 'ry' ) || 0 ); - var subpath = new THREE.Path(); + const x = parseFloatWithUnits( node.getAttribute( 'cx' ) || 0 ); + const y = parseFloatWithUnits( node.getAttribute( 'cy' ) || 0 ); + const rx = parseFloatWithUnits( node.getAttribute( 'rx' ) || 0 ); + const ry = parseFloatWithUnits( node.getAttribute( 'ry' ) || 0 ); + const subpath = new THREE.Path(); subpath.absellipse( x, y, rx, ry, 0, Math.PI * 2 ); - var path = new THREE.ShapePath(); + const path = new THREE.ShapePath(); path.subPaths.push( subpath ); return path; @@ -767,11 +770,11 @@ function parseLineNode( node ) { - var x1 = parseFloatWithUnits( node.getAttribute( 'x1' ) || 0 ); - var y1 = parseFloatWithUnits( node.getAttribute( 'y1' ) || 0 ); - var x2 = parseFloatWithUnits( node.getAttribute( 'x2' ) || 0 ); - var y2 = parseFloatWithUnits( node.getAttribute( 'y2' ) || 0 ); - var path = new THREE.ShapePath(); + const x1 = parseFloatWithUnits( node.getAttribute( 'x1' ) || 0 ); + const y1 = parseFloatWithUnits( node.getAttribute( 'y1' ) || 0 ); + const x2 = parseFloatWithUnits( node.getAttribute( 'x2' ) || 0 ); + const y2 = parseFloatWithUnits( node.getAttribute( 'y2' ) || 0 ); + const path = new THREE.ShapePath(); path.moveTo( x1, y1 ); path.lineTo( x2, y2 ); path.currentPath.autoClose = false; @@ -784,13 +787,13 @@ style = Object.assign( {}, style ); // clone style - var stylesheetStyles = {}; + let stylesheetStyles = {}; if ( node.hasAttribute( 'class' ) ) { - var classSelectors = node.getAttribute( 'class' ).split( /\s/ ).filter( Boolean ).map( i => i.trim() ); + const classSelectors = node.getAttribute( 'class' ).split( /\s/ ).filter( Boolean ).map( i => i.trim() ); - for ( var i = 0; i < classSelectors.length; i ++ ) { + for ( let i = 0; i < classSelectors.length; i ++ ) { stylesheetStyles = Object.assign( stylesheetStyles, stylesheets[ '.' + classSelectors[ i ] ] ); @@ -862,7 +865,7 @@ } // Character groups - var RE = { + const RE = { SEPARATOR: /[ \t\r\n\,.\-+]/, WHITESPACE: /[ \t\r\n]/, DIGIT: /[\d]/, @@ -873,19 +876,19 @@ FLAGS: /[01]/ }; // States - var SEP = 0; - var INT = 1; - var FLOAT = 2; - var EXP = 3; - var state = SEP; - var seenComma = true; - var result = [], - number = '', + const SEP = 0; + const INT = 1; + const FLOAT = 2; + const EXP = 3; + let state = SEP; + let seenComma = true; + let number = '', exponent = ''; + const result = []; function throwSyntaxError( current, i, partial ) { - var error = new SyntaxError( 'Unexpected character "' + current + '" at index ' + i + '.' ); + const error = new SyntaxError( 'Unexpected character "' + current + '" at index ' + i + '.' ); error.partial = partial; throw error; @@ -904,11 +907,10 @@ } - var current, - i = 0, - length = input.length; + let current; + const length = input.length; - for ( i = 0; i < length; i ++ ) { + for ( let i = 0; i < length; i ++ ) { current = input[ i ]; // check for flags @@ -1092,9 +1094,9 @@ } // Units - var units = [ 'mm', 'cm', 'in', 'pt', 'pc', 'px' ]; // Conversion: [ fromUnit ][ toUnit ] (-1 means dpi dependent) + const units = [ 'mm', 'cm', 'in', 'pt', 'pc', 'px' ]; // Conversion: [ fromUnit ][ toUnit ] (-1 means dpi dependent) - var unitConversion = { + const unitConversion = { 'mm': { 'mm': 1, 'cm': 0.1, @@ -1142,13 +1144,13 @@ function parseFloatWithUnits( string ) { - var theUnit = 'px'; + let theUnit = 'px'; if ( typeof string === 'string' || string instanceof String ) { - for ( var i = 0, n = units.length; i < n; i ++ ) { + for ( let i = 0, n = units.length; i < n; i ++ ) { - var u = units[ i ]; + const u = units[ i ]; if ( string.endsWith( u ) ) { @@ -1162,7 +1164,7 @@ } - var scale = undefined; + let scale = undefined; if ( theUnit === 'px' && scope.defaultUnit !== 'px' ) { @@ -1195,7 +1197,7 @@ } - var transform = parseNodeTransform( node ); + const transform = parseNodeTransform( node ); if ( transformStack.length > 0 ) { @@ -1211,32 +1213,32 @@ function parseNodeTransform( node ) { - var transform = new THREE.Matrix3(); - var currentTransform = tempTransform0; + const transform = new THREE.Matrix3(); + const currentTransform = tempTransform0; if ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) { - var tx = parseFloatWithUnits( node.getAttribute( 'x' ) ); - var ty = parseFloatWithUnits( node.getAttribute( 'y' ) ); + const tx = parseFloatWithUnits( node.getAttribute( 'x' ) ); + const ty = parseFloatWithUnits( node.getAttribute( 'y' ) ); transform.translate( tx, ty ); } if ( node.hasAttribute( 'transform' ) ) { - var transformsTexts = node.getAttribute( 'transform' ).split( ')' ); + const transformsTexts = node.getAttribute( 'transform' ).split( ')' ); - for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { + for ( let tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { - var transformText = transformsTexts[ tIndex ].trim(); + const transformText = transformsTexts[ tIndex ].trim(); if ( transformText === '' ) continue; - var openParPos = transformText.indexOf( '(' ); - var closeParPos = transformText.length; + const openParPos = transformText.indexOf( '(' ); + const closeParPos = transformText.length; if ( openParPos > 0 && openParPos < closeParPos ) { - var transformType = transformText.substr( 0, openParPos ); - var array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) ); + const transformType = transformText.substr( 0, openParPos ); + const array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) ); currentTransform.identity(); switch ( transformType ) { @@ -1244,8 +1246,8 @@ case 'translate': if ( array.length >= 1 ) { - var tx = array[ 0 ]; - var ty = tx; + const tx = array[ 0 ]; + let ty = tx; if ( array.length >= 2 ) { @@ -1262,9 +1264,9 @@ case 'rotate': if ( array.length >= 1 ) { - var angle = 0; - var cx = 0; - var cy = 0; // Angle + let angle = 0; + let cx = 0; + let cy = 0; // Angle angle = - array[ 0 ] * Math.PI / 180; @@ -1290,8 +1292,8 @@ case 'scale': if ( array.length >= 1 ) { - var scaleX = array[ 0 ]; - var scaleY = scaleX; + const scaleX = array[ 0 ]; + let scaleY = scaleX; if ( array.length >= 2 ) { @@ -1355,17 +1357,17 @@ } - var isRotated = isTransformRotated( m ); - var subPaths = path.subPaths; + const isRotated = isTransformRotated( m ); + const subPaths = path.subPaths; - for ( var i = 0, n = subPaths.length; i < n; i ++ ) { + for ( let i = 0, n = subPaths.length; i < n; i ++ ) { - var subPath = subPaths[ i ]; - var curves = subPath.curves; + const subPath = subPaths[ i ]; + const curves = subPath.curves; - for ( var j = 0; j < curves.length; j ++ ) { + for ( let j = 0; j < curves.length; j ++ ) { - var curve = curves[ j ]; + const curve = curves[ j ]; if ( curve.isLineCurve ) { @@ -1416,30 +1418,30 @@ function getTransformScaleX( m ) { - var te = m.elements; + const te = m.elements; return Math.sqrt( te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] ); } function getTransformScaleY( m ) { - var te = m.elements; + const te = m.elements; return Math.sqrt( te[ 3 ] * te[ 3 ] + te[ 4 ] * te[ 4 ] ); } // - var paths = []; - var stylesheets = {}; - var transformStack = []; - var tempTransform0 = new THREE.Matrix3(); - var tempTransform1 = new THREE.Matrix3(); - var tempTransform2 = new THREE.Matrix3(); - var tempTransform3 = new THREE.Matrix3(); - var tempV2 = new THREE.Vector2(); - var tempV3 = new THREE.Vector3(); - var currentTransform = new THREE.Matrix3(); - var xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml + const paths = []; + const stylesheets = {}; + const transformStack = []; + const tempTransform0 = new THREE.Matrix3(); + const tempTransform1 = new THREE.Matrix3(); + const tempTransform2 = new THREE.Matrix3(); + const tempTransform3 = new THREE.Matrix3(); + const tempV2 = new THREE.Vector2(); + const tempV3 = new THREE.Vector3(); + const currentTransform = new THREE.Matrix3(); + const xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml parseNode( xml.documentElement, { fill: '#000', @@ -1450,7 +1452,7 @@ strokeLineCap: 'butt', strokeMiterLimit: 4 } ); - var data = { + const data = { paths: paths, xml: xml.documentElement }; // console.log( paths ); @@ -1458,517 +1460,497 @@ return data; } - } ); - - SVGLoader.createShapes = function ( shapePath ) { - - // Param shapePath: a shapepath as returned by the parse function of this class - // Returns THREE.Shape object - const BIGNUMBER = 999999999; - const IntersectionLocationType = { - ORIGIN: 0, - DESTINATION: 1, - BETWEEN: 2, - LEFT: 3, - RIGHT: 4, - BEHIND: 5, - BEYOND: 6 - }; - const classifyResult = { - loc: IntersectionLocationType.ORIGIN, - t: 0 - }; - - function findEdgeIntersection( a0, a1, b0, b1 ) { - - var x1 = a0.x; - var x2 = a1.x; - var x3 = b0.x; - var x4 = b1.x; - var y1 = a0.y; - var y2 = a1.y; - var y3 = b0.y; - var y4 = b1.y; - var nom1 = ( x4 - x3 ) * ( y1 - y3 ) - ( y4 - y3 ) * ( x1 - x3 ); - var nom2 = ( x2 - x1 ) * ( y1 - y3 ) - ( y2 - y1 ) * ( x1 - x3 ); - var denom = ( y4 - y3 ) * ( x2 - x1 ) - ( x4 - x3 ) * ( y2 - y1 ); - var t1 = nom1 / denom; - var t2 = nom2 / denom; - - if ( denom === 0 && nom1 !== 0 || t1 <= 0 || t1 >= 1 || t2 < 0 || t2 > 1 ) { - - //1. lines are parallel or edges don't intersect - return null; - } else if ( nom1 === 0 && denom === 0 ) { + static createShapes( shapePath ) { + + // Param shapePath: a shapepath as returned by the parse function of this class + // Returns THREE.Shape object + const BIGNUMBER = 999999999; + const IntersectionLocationType = { + ORIGIN: 0, + DESTINATION: 1, + BETWEEN: 2, + LEFT: 3, + RIGHT: 4, + BEHIND: 5, + BEYOND: 6 + }; + const classifyResult = { + loc: IntersectionLocationType.ORIGIN, + t: 0 + }; - //2. lines are colinear - //check if endpoints of edge2 (b0-b1) lies on edge1 (a0-a1) - for ( var i = 0; i < 2; i ++ ) { + function findEdgeIntersection( a0, a1, b0, b1 ) { + + const x1 = a0.x; + const x2 = a1.x; + const x3 = b0.x; + const x4 = b1.x; + const y1 = a0.y; + const y2 = a1.y; + const y3 = b0.y; + const y4 = b1.y; + const nom1 = ( x4 - x3 ) * ( y1 - y3 ) - ( y4 - y3 ) * ( x1 - x3 ); + const nom2 = ( x2 - x1 ) * ( y1 - y3 ) - ( y2 - y1 ) * ( x1 - x3 ); + const denom = ( y4 - y3 ) * ( x2 - x1 ) - ( x4 - x3 ) * ( y2 - y1 ); + const t1 = nom1 / denom; + const t2 = nom2 / denom; + + if ( denom === 0 && nom1 !== 0 || t1 <= 0 || t1 >= 1 || t2 < 0 || t2 > 1 ) { + + //1. lines are parallel or edges don't intersect + return null; + + } else if ( nom1 === 0 && denom === 0 ) { - classifyPoint( i === 0 ? b0 : b1, a0, a1 ); //find position of this endpoints relatively to edge1 + //2. lines are colinear + //check if endpoints of edge2 (b0-b1) lies on edge1 (a0-a1) + for ( let i = 0; i < 2; i ++ ) { - if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { + classifyPoint( i === 0 ? b0 : b1, a0, a1 ); //find position of this endpoints relatively to edge1 - var point = i === 0 ? b0 : b1; - return { - x: point.x, - y: point.y, - t: classifyResult.t - }; + if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { - } else if ( classifyResult.loc == IntersectionLocationType.BETWEEN ) { + const point = i === 0 ? b0 : b1; + return { + x: point.x, + y: point.y, + t: classifyResult.t + }; - var x = + ( x1 + classifyResult.t * ( x2 - x1 ) ).toPrecision( 10 ); - var y = + ( y1 + classifyResult.t * ( y2 - y1 ) ).toPrecision( 10 ); - return { - x: x, - y: y, - t: classifyResult.t - }; + } else if ( classifyResult.loc == IntersectionLocationType.BETWEEN ) { + + const x = + ( x1 + classifyResult.t * ( x2 - x1 ) ).toPrecision( 10 ); + const y = + ( y1 + classifyResult.t * ( y2 - y1 ) ).toPrecision( 10 ); + return { + x: x, + y: y, + t: classifyResult.t + }; + + } } - } + return null; - return null; + } else { - } else { + //3. edges intersect + for ( let i = 0; i < 2; i ++ ) { - //3. edges intersect - for ( var i = 0; i < 2; i ++ ) { + classifyPoint( i === 0 ? b0 : b1, a0, a1 ); - classifyPoint( i === 0 ? b0 : b1, a0, a1 ); + if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { - if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { + const point = i === 0 ? b0 : b1; + return { + x: point.x, + y: point.y, + t: classifyResult.t + }; - var point = i === 0 ? b0 : b1; - return { - x: point.x, - y: point.y, - t: classifyResult.t - }; + } } - } + const x = + ( x1 + t1 * ( x2 - x1 ) ).toPrecision( 10 ); + const y = + ( y1 + t1 * ( y2 - y1 ) ).toPrecision( 10 ); + return { + x: x, + y: y, + t: t1 + }; - var x = + ( x1 + t1 * ( x2 - x1 ) ).toPrecision( 10 ); - var y = + ( y1 + t1 * ( y2 - y1 ) ).toPrecision( 10 ); - return { - x: x, - y: y, - t: t1 - }; + } } - } + function classifyPoint( p, edgeStart, edgeEnd ) { - function classifyPoint( p, edgeStart, edgeEnd ) { + const ax = edgeEnd.x - edgeStart.x; + const ay = edgeEnd.y - edgeStart.y; + const bx = p.x - edgeStart.x; + const by = p.y - edgeStart.y; + const sa = ax * by - bx * ay; - var ax = edgeEnd.x - edgeStart.x; - var ay = edgeEnd.y - edgeStart.y; - var bx = p.x - edgeStart.x; - var by = p.y - edgeStart.y; - var sa = ax * by - bx * ay; + if ( p.x === edgeStart.x && p.y === edgeStart.y ) { - if ( p.x === edgeStart.x && p.y === edgeStart.y ) { + classifyResult.loc = IntersectionLocationType.ORIGIN; + classifyResult.t = 0; + return; - classifyResult.loc = IntersectionLocationType.ORIGIN; - classifyResult.t = 0; - return; + } - } + if ( p.x === edgeEnd.x && p.y === edgeEnd.y ) { - if ( p.x === edgeEnd.x && p.y === edgeEnd.y ) { + classifyResult.loc = IntersectionLocationType.DESTINATION; + classifyResult.t = 1; + return; - classifyResult.loc = IntersectionLocationType.DESTINATION; - classifyResult.t = 1; - return; + } - } + if ( sa < - Number.EPSILON ) { - if ( sa < - Number.EPSILON ) { + classifyResult.loc = IntersectionLocationType.LEFT; + return; - classifyResult.loc = IntersectionLocationType.LEFT; - return; + } - } + if ( sa > Number.EPSILON ) { - if ( sa > Number.EPSILON ) { + classifyResult.loc = IntersectionLocationType.RIGHT; + return; - classifyResult.loc = IntersectionLocationType.RIGHT; - return; + } - } + if ( ax * bx < 0 || ay * by < 0 ) { - if ( ax * bx < 0 || ay * by < 0 ) { + classifyResult.loc = IntersectionLocationType.BEHIND; + return; - classifyResult.loc = IntersectionLocationType.BEHIND; - return; + } - } + if ( Math.sqrt( ax * ax + ay * ay ) < Math.sqrt( bx * bx + by * by ) ) { - if ( Math.sqrt( ax * ax + ay * ay ) < Math.sqrt( bx * bx + by * by ) ) { + classifyResult.loc = IntersectionLocationType.BEYOND; + return; - classifyResult.loc = IntersectionLocationType.BEYOND; - return; + } - } + let t; - var t; + if ( ax !== 0 ) { - if ( ax !== 0 ) { + t = bx / ax; - t = bx / ax; + } else { - } else { + t = by / ay; - t = by / ay; + } - } + classifyResult.loc = IntersectionLocationType.BETWEEN; + classifyResult.t = t; - classifyResult.loc = IntersectionLocationType.BETWEEN; - classifyResult.t = t; + } - } + function getIntersections( path1, path2 ) { - function getIntersections( path1, path2 ) { + const intersectionsRaw = []; + const intersections = []; - const intersectionsRaw = []; - const intersections = []; + for ( let index = 1; index < path1.length; index ++ ) { - for ( let index = 1; index < path1.length; index ++ ) { + const path1EdgeStart = path1[ index - 1 ]; + const path1EdgeEnd = path1[ index ]; - const path1EdgeStart = path1[ index - 1 ]; - const path1EdgeEnd = path1[ index ]; + for ( let index2 = 1; index2 < path2.length; index2 ++ ) { - for ( let index2 = 1; index2 < path2.length; index2 ++ ) { + const path2EdgeStart = path2[ index2 - 1 ]; + const path2EdgeEnd = path2[ index2 ]; + const intersection = findEdgeIntersection( path1EdgeStart, path1EdgeEnd, path2EdgeStart, path2EdgeEnd ); - const path2EdgeStart = path2[ index2 - 1 ]; - const path2EdgeEnd = path2[ index2 ]; - const intersection = findEdgeIntersection( path1EdgeStart, path1EdgeEnd, path2EdgeStart, path2EdgeEnd ); + if ( intersection !== null && intersectionsRaw.find( i => i.t <= intersection.t + Number.EPSILON && i.t >= intersection.t - Number.EPSILON ) === undefined ) { - if ( intersection !== null && intersectionsRaw.find( i => i.t <= intersection.t + Number.EPSILON && i.t >= intersection.t - Number.EPSILON ) === undefined ) { + intersectionsRaw.push( intersection ); + intersections.push( new THREE.Vector2( intersection.x, intersection.y ) ); - intersectionsRaw.push( intersection ); - intersections.push( new THREE.Vector2( intersection.x, intersection.y ) ); + } } } - } + return intersections; - return intersections; + } - } + function getScanlineIntersections( scanline, boundingBox, paths ) { - function getScanlineIntersections( scanline, boundingBox, paths ) { + const center = new THREE.Vector2(); + boundingBox.getCenter( center ); + const allIntersections = []; + paths.forEach( path => { - const center = new THREE.Vector2(); - boundingBox.getCenter( center ); - const allIntersections = []; - paths.forEach( path => { + // check if the center of the bounding box is in the bounding box of the paths. + // this is a pruning method to limit the search of intersections in paths that can't envelop of the current path. + // if a path envelops another path. The center of that oter path, has to be inside the bounding box of the enveloping path. + if ( path.boundingBox.containsPoint( center ) ) { - // check if the center of the bounding box is in the bounding box of the paths. - // this is a pruning method to limit the search of intersections in paths that can't envelop of the current path. - // if a path envelops another path. The center of that oter path, has to be inside the bounding box of the enveloping path. - if ( path.boundingBox.containsPoint( center ) ) { + const intersections = getIntersections( scanline, path.points ); + intersections.forEach( p => { - const intersections = getIntersections( scanline, path.points ); - intersections.forEach( p => { + allIntersections.push( { + identifier: path.identifier, + isCW: path.isCW, + point: p + } ); - allIntersections.push( { - identifier: path.identifier, - isCW: path.isCW, - point: p } ); - } ); + } - } + } ); + allIntersections.sort( ( i1, i2 ) => { - } ); - allIntersections.sort( ( i1, i2 ) => { + return i1.point.x - i2.point.x; - return i1.point.x - i2.point.x; + } ); + return allIntersections; - } ); - return allIntersections; + } - } + function isHoleTo( simplePath, allPaths, scanlineMinX, scanlineMaxX, _fillRule ) { - function isHoleTo( simplePath, allPaths, scanlineMinX, scanlineMaxX, _fillRule ) { + if ( _fillRule === null || _fillRule === undefined || _fillRule === '' ) { - if ( _fillRule === null || _fillRule === undefined || _fillRule === '' ) { + _fillRule = 'nonzero'; - _fillRule = 'nonzero'; + } - } + const centerBoundingBox = new THREE.Vector2(); + simplePath.boundingBox.getCenter( centerBoundingBox ); + const scanline = [ new THREE.Vector2( scanlineMinX, centerBoundingBox.y ), new THREE.Vector2( scanlineMaxX, centerBoundingBox.y ) ]; + const scanlineIntersections = getScanlineIntersections( scanline, simplePath.boundingBox, allPaths ); + scanlineIntersections.sort( ( i1, i2 ) => { - const centerBoundingBox = new THREE.Vector2(); - simplePath.boundingBox.getCenter( centerBoundingBox ); - const scanline = [ new THREE.Vector2( scanlineMinX, centerBoundingBox.y ), new THREE.Vector2( scanlineMaxX, centerBoundingBox.y ) ]; - const scanlineIntersections = getScanlineIntersections( scanline, simplePath.boundingBox, allPaths ); - scanlineIntersections.sort( ( i1, i2 ) => { + return i1.point.x - i2.point.x; - return i1.point.x - i2.point.x; + } ); + const baseIntersections = []; + const otherIntersections = []; + scanlineIntersections.forEach( i => { - } ); - const baseIntersections = []; - const otherIntersections = []; - scanlineIntersections.forEach( i => { + if ( i.identifier === simplePath.identifier ) { - if ( i.identifier === simplePath.identifier ) { + baseIntersections.push( i ); - baseIntersections.push( i ); + } else { - } else { + otherIntersections.push( i ); - otherIntersections.push( i ); + } - } + } ); + const firstXOfPath = baseIntersections[ 0 ].point.x; // build up the path hierarchy - } ); - const firstXOfPath = baseIntersections[ 0 ].point.x; // build up the path hierarchy + const stack = []; + let i = 0; - const stack = []; - let i = 0; + while ( i < otherIntersections.length && otherIntersections[ i ].point.x < firstXOfPath ) { - while ( i < otherIntersections.length && otherIntersections[ i ].point.x < firstXOfPath ) { + if ( stack.length > 0 && stack[ stack.length - 1 ] === otherIntersections[ i ].identifier ) { - if ( stack.length > 0 && stack[ stack.length - 1 ] === otherIntersections[ i ].identifier ) { + stack.pop(); - stack.pop(); + } else { - } else { + stack.push( otherIntersections[ i ].identifier ); - stack.push( otherIntersections[ i ].identifier ); + } - } + i ++; - i ++; + } - } + stack.push( simplePath.identifier ); - stack.push( simplePath.identifier ); + if ( _fillRule === 'evenodd' ) { - if ( _fillRule === 'evenodd' ) { + const isHole = stack.length % 2 === 0 ? true : false; + const isHoleFor = stack[ stack.length - 2 ]; + return { + identifier: simplePath.identifier, + isHole: isHole, + for: isHoleFor + }; - const isHole = stack.length % 2 === 0 ? true : false; - const isHoleFor = stack[ stack.length - 2 ]; - return { - identifier: simplePath.identifier, - isHole: isHole, - for: isHoleFor - }; + } else if ( _fillRule === 'nonzero' ) { - } else if ( _fillRule === 'nonzero' ) { + // check if path is a hole by counting the amount of paths with alternating rotations it has to cross. + let isHole = true; + let isHoleFor = null; + let lastCWValue = null; - // check if path is a hole by counting the amount of paths with alternating rotations it has to cross. - let isHole = true; - let isHoleFor = null; - let lastCWValue = null; + for ( let i = 0; i < stack.length; i ++ ) { - for ( let i = 0; i < stack.length; i ++ ) { + const identifier = stack[ i ]; - const identifier = stack[ i ]; + if ( isHole ) { - if ( isHole ) { + lastCWValue = allPaths[ identifier ].isCW; + isHole = false; + isHoleFor = identifier; - lastCWValue = allPaths[ identifier ].isCW; - isHole = false; - isHoleFor = identifier; + } else if ( lastCWValue !== allPaths[ identifier ].isCW ) { - } else if ( lastCWValue !== allPaths[ identifier ].isCW ) { + lastCWValue = allPaths[ identifier ].isCW; + isHole = true; - lastCWValue = allPaths[ identifier ].isCW; - isHole = true; + } } + return { + identifier: simplePath.identifier, + isHole: isHole, + for: isHoleFor + }; + + } else { + + console.warn( 'fill-rule: "' + _fillRule + '" is currently not implemented.' ); + } - return { - identifier: simplePath.identifier, - isHole: isHole, - for: isHoleFor - }; + } // check for self intersecting paths + // TODO + // check intersecting paths + // TODO + // prepare paths for hole detection - } else { - console.warn( 'fill-rule: "' + _fillRule + '" is currently not implemented.' ); + let identifier = 0; + let scanlineMinX = BIGNUMBER; + let scanlineMaxX = - BIGNUMBER; + let simplePaths = shapePath.subPaths.map( p => { - } + const points = p.getPoints(); + let maxY = - BIGNUMBER; + let minY = BIGNUMBER; + let maxX = - BIGNUMBER; + let minX = BIGNUMBER; //points.forEach(p => p.y *= -1); - } // check for self intersecting paths - // TODO - // check intersecting paths - // TODO - // prepare paths for hole detection + for ( let i = 0; i < points.length; i ++ ) { + const p = points[ i ]; - let identifier = 0; - let scanlineMinX = BIGNUMBER; - let scanlineMaxX = - BIGNUMBER; - let simplePaths = shapePath.subPaths.map( p => { + if ( p.y > maxY ) { - const points = p.getPoints(); - let maxY = - BIGNUMBER; - let minY = BIGNUMBER; - let maxX = - BIGNUMBER; - let minX = BIGNUMBER; //points.forEach(p => p.y *= -1); + maxY = p.y; - for ( let i = 0; i < points.length; i ++ ) { + } + + if ( p.y < minY ) { - const p = points[ i ]; + minY = p.y; - if ( p.y > maxY ) { + } - maxY = p.y; + if ( p.x > maxX ) { - } + maxX = p.x; - if ( p.y < minY ) { + } - minY = p.y; + if ( p.x < minX ) { - } + minX = p.x; + + } - if ( p.x > maxX ) { + } // - maxX = p.x; + + if ( scanlineMaxX <= maxX ) { + + scanlineMaxX = maxX + 1; } - if ( p.x < minX ) { + if ( scanlineMinX >= minX ) { - minX = p.x; + scanlineMinX = minX - 1; } - } // + return { + points: points, + isCW: THREE.ShapeUtils.isClockWise( points ), + identifier: identifier ++, + boundingBox: new THREE.Box2( new THREE.Vector2( minX, minY ), new THREE.Vector2( maxX, maxY ) ) + }; + } ); + simplePaths = simplePaths.filter( sp => sp.points.length > 0 ); // check if path is solid or a hole - if ( scanlineMaxX <= maxX ) { + const isAHole = simplePaths.map( p => isHoleTo( p, simplePaths, scanlineMinX, scanlineMaxX, shapePath.userData.style.fillRule ) ); + const shapesToReturn = []; + simplePaths.forEach( p => { - scanlineMaxX = maxX + 1; + const amIAHole = isAHole[ p.identifier ]; - } + if ( ! amIAHole.isHole ) { - if ( scanlineMinX >= minX ) { + const shape = new THREE.Shape( p.points ); + const holes = isAHole.filter( h => h.isHole && h.for === p.identifier ); + holes.forEach( h => { - scanlineMinX = minX - 1; + const path = simplePaths[ h.identifier ]; + shape.holes.push( new THREE.Path( path.points ) ); - } + } ); + shapesToReturn.push( shape ); - return { - points: points, - isCW: THREE.ShapeUtils.isClockWise( points ), - identifier: identifier ++, - boundingBox: new THREE.Box2( new THREE.Vector2( minX, minY ), new THREE.Vector2( maxX, maxY ) ) - }; + } + + } ); + return shapesToReturn; - } ); - simplePaths = simplePaths.filter( sp => sp.points.length > 0 ); // check if path is solid or a hole + } - const isAHole = simplePaths.map( p => isHoleTo( p, simplePaths, scanlineMinX, scanlineMaxX, shapePath.userData.style.fillRule ) ); - const shapesToReturn = []; - simplePaths.forEach( p => { + static getStrokeStyle( width, color, lineJoin, lineCap, miterLimit ) { + + // Param width: Stroke width + // Param color: As returned by THREE.Color.getStyle() + // Param lineJoin: One of "round", "bevel", "miter" or "miter-limit" + // Param lineCap: One of "round", "square" or "butt" + // Param miterLimit: Maximum join length, in multiples of the "width" parameter (join is truncated if it exceeds that distance) + // Returns style object + width = width !== undefined ? width : 1; + color = color !== undefined ? color : '#000'; + lineJoin = lineJoin !== undefined ? lineJoin : 'miter'; + lineCap = lineCap !== undefined ? lineCap : 'butt'; + miterLimit = miterLimit !== undefined ? miterLimit : 4; + return { + strokeColor: color, + strokeWidth: width, + strokeLineJoin: lineJoin, + strokeLineCap: lineCap, + strokeMiterLimit: miterLimit + }; - const amIAHole = isAHole[ p.identifier ]; + } - if ( ! amIAHole.isHole ) { + static pointsToStroke( points, style, arcDivisions, minDistance ) { - const shape = new THREE.Shape( p.points ); - const holes = isAHole.filter( h => h.isHole && h.for === p.identifier ); - holes.forEach( h => { + // Generates a stroke with some witdh around the given path. + // The path can be open or closed (last point equals to first point) + // Param points: Array of Vector2D (the path). Minimum 2 points. + // Param style: Object with SVG properties as returned by SVGLoader.getStrokeStyle(), or SVGLoader.parse() in the path.userData.style object + // Params arcDivisions: Arc divisions for round joins and endcaps. (Optional) + // Param minDistance: Points closer to this distance will be merged. (Optional) + // Returns THREE.BufferGeometry with stroke triangles (In plane z = 0). UV coordinates are generated ('u' along path. 'v' across it, from left to right) + const vertices = []; + const normals = []; + const uvs = []; - const path = simplePaths[ h.identifier ]; - shape.holes.push( new THREE.Path( path.points ) ); + if ( SVGLoader.pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs ) === 0 ) { - } ); - shapesToReturn.push( shape ); + return null; } - } ); - return shapesToReturn; - - }; - - SVGLoader.getStrokeStyle = function ( width, color, lineJoin, lineCap, miterLimit ) { - - // Param width: Stroke width - // Param color: As returned by THREE.Color.getStyle() - // Param lineJoin: One of "round", "bevel", "miter" or "miter-limit" - // Param lineCap: One of "round", "square" or "butt" - // Param miterLimit: Maximum join length, in multiples of the "width" parameter (join is truncated if it exceeds that distance) - // Returns style object - width = width !== undefined ? width : 1; - color = color !== undefined ? color : '#000'; - lineJoin = lineJoin !== undefined ? lineJoin : 'miter'; - lineCap = lineCap !== undefined ? lineCap : 'butt'; - miterLimit = miterLimit !== undefined ? miterLimit : 4; - return { - strokeColor: color, - strokeWidth: width, - strokeLineJoin: lineJoin, - strokeLineCap: lineCap, - strokeMiterLimit: miterLimit - }; - - }; - - SVGLoader.pointsToStroke = function ( points, style, arcDivisions, minDistance ) { - - // Generates a stroke with some witdh around the given path. - // The path can be open or closed (last point equals to first point) - // Param points: Array of Vector2D (the path). Minimum 2 points. - // Param style: Object with SVG properties as returned by SVGLoader.getStrokeStyle(), or SVGLoader.parse() in the path.userData.style object - // Params arcDivisions: Arc divisions for round joins and endcaps. (Optional) - // Param minDistance: Points closer to this distance will be merged. (Optional) - // Returns THREE.BufferGeometry with stroke triangles (In plane z = 0). UV coordinates are generated ('u' along path. 'v' across it, from left to right) - var vertices = []; - var normals = []; - var uvs = []; - - if ( SVGLoader.pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs ) === 0 ) { - - return null; + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); + geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) ); + return geometry; } - var geometry = new THREE.BufferGeometry(); - geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); - geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) ); - return geometry; - - }; - - SVGLoader.pointsToStrokeWithBuffers = function () { - - var tempV2_1 = new THREE.Vector2(); - var tempV2_2 = new THREE.Vector2(); - var tempV2_3 = new THREE.Vector2(); - var tempV2_4 = new THREE.Vector2(); - var tempV2_5 = new THREE.Vector2(); - var tempV2_6 = new THREE.Vector2(); - var tempV2_7 = new THREE.Vector2(); - var lastPointL = new THREE.Vector2(); - var lastPointR = new THREE.Vector2(); - var point0L = new THREE.Vector2(); - var point0R = new THREE.Vector2(); - var currentPointL = new THREE.Vector2(); - var currentPointR = new THREE.Vector2(); - var nextPointL = new THREE.Vector2(); - var nextPointR = new THREE.Vector2(); - var innerPoint = new THREE.Vector2(); - var outerPoint = new THREE.Vector2(); - return function ( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { + static pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { // This function can be called to update existing arrays or buffers. // Accepts same parameters as pointsToStroke, plus the buffers and optional offset. @@ -1976,27 +1958,45 @@ // Returns number of written vertices / normals / uvs pairs // if 'vertices' parameter is undefined no triangles will be generated, but the returned vertices count will still be valid (useful to preallocate the buffers) // 'normals' and 'uvs' buffers are optional + const tempV2_1 = new THREE.Vector2(); + const tempV2_2 = new THREE.Vector2(); + const tempV2_3 = new THREE.Vector2(); + const tempV2_4 = new THREE.Vector2(); + const tempV2_5 = new THREE.Vector2(); + const tempV2_6 = new THREE.Vector2(); + const tempV2_7 = new THREE.Vector2(); + const lastPointL = new THREE.Vector2(); + const lastPointR = new THREE.Vector2(); + const point0L = new THREE.Vector2(); + const point0R = new THREE.Vector2(); + const currentPointL = new THREE.Vector2(); + const currentPointR = new THREE.Vector2(); + const nextPointL = new THREE.Vector2(); + const nextPointR = new THREE.Vector2(); + const innerPoint = new THREE.Vector2(); + const outerPoint = new THREE.Vector2(); arcDivisions = arcDivisions !== undefined ? arcDivisions : 12; minDistance = minDistance !== undefined ? minDistance : 0.001; vertexOffset = vertexOffset !== undefined ? vertexOffset : 0; // First ensure there are no duplicated points points = removeDuplicatedPoints( points ); - var numPoints = points.length; + const numPoints = points.length; if ( numPoints < 2 ) return 0; - var isClosed = points[ 0 ].equals( points[ numPoints - 1 ] ); - var currentPoint; - var previousPoint = points[ 0 ]; - var nextPoint; - var strokeWidth2 = style.strokeWidth / 2; - var deltaU = 1 / ( numPoints - 1 ); - var u0 = 0; - var innerSideModified; - var joinIsOnLeftSide; - var isMiter; - var initialJoinIsOnLeftSide = false; - var numVertices = 0; - var currentCoordinate = vertexOffset * 3; - var currentCoordinateUV = vertexOffset * 2; // Get initial left and right stroke points + const isClosed = points[ 0 ].equals( points[ numPoints - 1 ] ); + let currentPoint; + let previousPoint = points[ 0 ]; + let nextPoint; + const strokeWidth2 = style.strokeWidth / 2; + const deltaU = 1 / ( numPoints - 1 ); + let u0 = 0, + u1; + let innerSideModified; + let joinIsOnLeftSide; + let isMiter; + let initialJoinIsOnLeftSide = false; + let numVertices = 0; + let currentCoordinate = vertexOffset * 3; + let currentCoordinateUV = vertexOffset * 2; // Get initial left and right stroke points getNormal( points[ 0 ], points[ 1 ], tempV2_1 ).multiplyScalar( strokeWidth2 ); lastPointL.copy( points[ 0 ] ).sub( tempV2_1 ); @@ -2004,7 +2004,7 @@ point0L.copy( lastPointL ); point0R.copy( lastPointR ); - for ( var iPoint = 1; iPoint < numPoints; iPoint ++ ) { + for ( let iPoint = 1; iPoint < numPoints; iPoint ++ ) { currentPoint = points[ iPoint ]; // Get next point @@ -2024,12 +2024,12 @@ } // Normal of previous segment in tempV2_1 - var normal1 = tempV2_1; + const normal1 = tempV2_1; getNormal( previousPoint, currentPoint, normal1 ); tempV2_3.copy( normal1 ).multiplyScalar( strokeWidth2 ); currentPointL.copy( currentPoint ).sub( tempV2_3 ); currentPointR.copy( currentPoint ).add( tempV2_3 ); - var u1 = u0 + deltaU; + u1 = u0 + deltaU; innerSideModified = false; if ( nextPoint !== undefined ) { @@ -2051,21 +2051,21 @@ if ( iPoint === 1 ) initialJoinIsOnLeftSide = joinIsOnLeftSide; tempV2_3.subVectors( nextPoint, currentPoint ); tempV2_3.normalize(); - var dot = Math.abs( normal1.dot( tempV2_3 ) ); // If path is straight, don't create join + const dot = Math.abs( normal1.dot( tempV2_3 ) ); // If path is straight, don't create join if ( dot !== 0 ) { // Compute inner and outer segment intersections - var miterSide = strokeWidth2 / dot; + const miterSide = strokeWidth2 / dot; tempV2_3.multiplyScalar( - miterSide ); tempV2_4.subVectors( currentPoint, previousPoint ); tempV2_5.copy( tempV2_4 ).setLength( miterSide ).add( tempV2_3 ); innerPoint.copy( tempV2_5 ).negate(); - var miterLength2 = tempV2_5.length(); - var segmentLengthPrev = tempV2_4.length(); + const miterLength2 = tempV2_5.length(); + const segmentLengthPrev = tempV2_4.length(); tempV2_4.divideScalar( segmentLengthPrev ); tempV2_6.subVectors( nextPoint, currentPoint ); - var segmentLengthNext = tempV2_6.length(); + const segmentLengthNext = tempV2_6.length(); tempV2_6.divideScalar( segmentLengthNext ); // Check that previous and next segments doesn't overlap with the innerPoint of intersection if ( tempV2_4.dot( innerPoint ) < segmentLengthPrev && tempV2_6.dot( innerPoint ) < segmentLengthNext ) { @@ -2124,7 +2124,7 @@ case 'miter': case 'miter-clip': default: - var miterFraction = strokeWidth2 * style.strokeMiterLimit / miterLength2; + const miterFraction = strokeWidth2 * style.strokeMiterLimit / miterLength2; if ( miterFraction < 1 ) { @@ -2277,8 +2277,8 @@ } else if ( innerSideModified && vertices ) { // Modify path first segment vertices to adjust to the segments inner and outer intersections - var lastOuter = outerPoint; - var lastInner = innerPoint; + let lastOuter = outerPoint; + let lastInner = innerPoint; if ( initialJoinIsOnLeftSide !== joinIsOnLeftSide ) { @@ -2369,13 +2369,13 @@ // p1 and p2 are in clockwise direction. tempV2_1.copy( p1 ).sub( center ).normalize(); tempV2_2.copy( p2 ).sub( center ).normalize(); - var angle = Math.PI; - var dot = tempV2_1.dot( tempV2_2 ); + let angle = Math.PI; + const dot = tempV2_1.dot( tempV2_2 ); if ( Math.abs( dot ) < 1 ) angle = Math.abs( Math.acos( dot ) ); angle /= arcDivisions; tempV2_3.copy( p1 ); - for ( var i = 0, il = arcDivisions - 1; i < il; i ++ ) { + for ( let i = 0, il = arcDivisions - 1; i < il; i ++ ) { tempV2_4.copy( tempV2_3 ).rotateAround( center, angle ); addVertex( tempV2_3, u, v ); @@ -2545,7 +2545,7 @@ tempV2_2.set( tempV2_1.y, - tempV2_1.x ); tempV2_3.addVectors( tempV2_1, tempV2_2 ).add( center ); tempV2_4.subVectors( tempV2_2, tempV2_1 ).add( center ); - var vl = vertices.length; // Modify already existing vertices + const vl = vertices.length; // Modify already existing vertices if ( joinIsOnLeftSide ) { @@ -2578,9 +2578,9 @@ // Creates a new array if necessary with duplicated points removed. // This does not remove duplicated initial and ending points of a closed path. - var dupPoints = false; + let dupPoints = false; - for ( var i = 1, n = points.length - 1; i < n; i ++ ) { + for ( let i = 1, n = points.length - 1; i < n; i ++ ) { if ( points[ i ].distanceTo( points[ i + 1 ] ) < minDistance ) { @@ -2592,10 +2592,10 @@ } if ( ! dupPoints ) return points; - var newPoints = []; + const newPoints = []; newPoints.push( points[ 0 ] ); - for ( var i = 1, n = points.length - 1; i < n; i ++ ) { + for ( let i = 1, n = points.length - 1; i < n; i ++ ) { if ( points[ i ].distanceTo( points[ i + 1 ] ) >= minDistance ) { @@ -2610,9 +2610,9 @@ } - }; + } - }(); + } THREE.SVGLoader = SVGLoader; diff --git a/examples/jsm/loaders/GLTFLoader.js b/examples/jsm/loaders/GLTFLoader.js index 38c9919a394061..6837c3fac0a2fe 100644 --- a/examples/jsm/loaders/GLTFLoader.js +++ b/examples/jsm/loaders/GLTFLoader.js @@ -63,11 +63,11 @@ import { sRGBEncoding } from '../../../build/three.module.js'; -var GLTFLoader = ( function () { +class GLTFLoader extends Loader { - function GLTFLoader( manager ) { + constructor( manager ) { - Loader.call( this, manager ); + super( manager ); this.dracoLoader = null; this.ktx2Loader = null; @@ -113,320 +113,318 @@ var GLTFLoader = ( function () { } - GLTFLoader.prototype = Object.assign( Object.create( Loader.prototype ), { + load( url, onLoad, onProgress, onError ) { - constructor: GLTFLoader, + const scope = this; - load: function ( url, onLoad, onProgress, onError ) { + let resourcePath; - var scope = this; + if ( this.resourcePath !== '' ) { - var resourcePath; + resourcePath = this.resourcePath; - if ( this.resourcePath !== '' ) { + } else if ( this.path !== '' ) { - resourcePath = this.resourcePath; + resourcePath = this.path; - } else if ( this.path !== '' ) { - - resourcePath = this.path; - - } else { - - resourcePath = LoaderUtils.extractUrlBase( url ); + } else { - } + resourcePath = LoaderUtils.extractUrlBase( url ); - // Tells the LoadingManager to track an extra item, which resolves after - // the model is fully loaded. This means the count of items loaded will - // be incorrect, but ensures manager.onLoad() does not fire early. - this.manager.itemStart( url ); + } - var _onError = function ( e ) { + // 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 ); - if ( onError ) { + const _onError = function ( e ) { - onError( e ); + if ( onError ) { - } else { + onError( e ); - console.error( e ); + } else { - } + console.error( e ); - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); + } - }; + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); - var loader = new FileLoader( this.manager ); + }; - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); + const loader = new FileLoader( this.manager ); - loader.load( url, function ( data ) { + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); - try { + loader.load( url, function ( data ) { - scope.parse( data, resourcePath, function ( gltf ) { + try { - onLoad( gltf ); + scope.parse( data, resourcePath, function ( gltf ) { - scope.manager.itemEnd( url ); + onLoad( gltf ); - }, _onError ); + scope.manager.itemEnd( url ); - } catch ( e ) { + }, _onError ); - _onError( e ); + } catch ( e ) { - } + _onError( e ); - }, onProgress, _onError ); + } - }, + }, onProgress, _onError ); - setDRACOLoader: function ( dracoLoader ) { + } - this.dracoLoader = dracoLoader; - return this; + setDRACOLoader( dracoLoader ) { - }, + this.dracoLoader = dracoLoader; + return this; - setDDSLoader: function () { + } - throw new Error( + setDDSLoader() { - 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' + throw new Error( - ); + 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' - }, + ); - setKTX2Loader: function ( ktx2Loader ) { + } - this.ktx2Loader = ktx2Loader; - return this; + setKTX2Loader( ktx2Loader ) { - }, + this.ktx2Loader = ktx2Loader; + return this; - setMeshoptDecoder: function ( meshoptDecoder ) { + } - this.meshoptDecoder = meshoptDecoder; - return this; + setMeshoptDecoder( meshoptDecoder ) { - }, + this.meshoptDecoder = meshoptDecoder; + return this; - register: function ( callback ) { + } - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + register( callback ) { - this.pluginCallbacks.push( callback ); + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - } + this.pluginCallbacks.push( callback ); - return this; + } - }, + return this; - unregister: function ( callback ) { + } - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { + unregister( callback ) { - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - } + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - return this; + } - }, + return this; - parse: function ( data, path, onLoad, onError ) { + } - var content; - var extensions = {}; - var plugins = {}; + parse( data, path, onLoad, onError ) { - if ( typeof data === 'string' ) { + let content; + const extensions = {}; + const plugins = {}; - content = data; + if ( typeof data === 'string' ) { - } else { + content = data; - var magic = LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); + } else { - if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { + const magic = LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); - try { + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { - extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); + try { - } catch ( error ) { + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); - if ( onError ) onError( error ); - return; + } catch ( error ) { - } + if ( onError ) onError( error ); + return; - content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; + } - } else { + content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; - content = LoaderUtils.decodeText( new Uint8Array( data ) ); + } else { - } + content = LoaderUtils.decodeText( new Uint8Array( data ) ); } - var json = JSON.parse( content ); + } - if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { + const json = JSON.parse( content ); - if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); - return; + 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, - requestHeader: this.requestHeader, - manager: this.manager, - ktx2Loader: this.ktx2Loader, - meshoptDecoder: this.meshoptDecoder + const parser = new GLTFParser( json, { - } ); + path: path || this.resourcePath || '', + crossOrigin: this.crossOrigin, + requestHeader: this.requestHeader, + manager: this.manager, + ktx2Loader: this.ktx2Loader, + meshoptDecoder: this.meshoptDecoder - parser.fileLoader.setRequestHeader( this.requestHeader ); + } ); - for ( var i = 0; i < this.pluginCallbacks.length; i ++ ) { + parser.fileLoader.setRequestHeader( this.requestHeader ); - var plugin = this.pluginCallbacks[ i ]( parser ); - plugins[ plugin.name ] = plugin; + for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { - // 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; + const 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 ) { + if ( json.extensionsUsed ) { - var extensionName = json.extensionsUsed[ i ]; - var extensionsRequired = json.extensionsRequired || []; + for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { - switch ( extensionName ) { + const extensionName = json.extensionsUsed[ i ]; + const extensionsRequired = json.extensionsRequired || []; - case EXTENSIONS.KHR_MATERIALS_UNLIT: - extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); - break; + switch ( extensionName ) { - case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: - extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); - break; + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); + break; - case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: - extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); - break; + case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: + extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); + break; - case EXTENSIONS.KHR_TEXTURE_TRANSFORM: - extensions[ extensionName ] = new GLTFTextureTransformExtension(); - break; + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); + break; - case EXTENSIONS.KHR_MESH_QUANTIZATION: - extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); - break; + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ extensionName ] = new GLTFTextureTransformExtension(); + break; - default: + case EXTENSIONS.KHR_MESH_QUANTIZATION: + extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); + break; - if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) { + default: - console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); + 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 ); - } - } ); + parser.setExtensions( extensions ); + parser.setPlugins( plugins ); + parser.parse( onLoad, onError ); - /* GLTFREGISTRY */ + } - function GLTFRegistry() { +} - var objects = {}; +/* GLTFREGISTRY */ - return { +function GLTFRegistry() { - get: function ( key ) { + let objects = {}; - return objects[ key ]; + return { - }, + get: function ( key ) { - add: function ( key, object ) { + return objects[ key ]; - objects[ key ] = object; + }, - }, + add: function ( key, object ) { - remove: function ( key ) { + objects[ key ] = object; - delete objects[ key ]; + }, - }, + remove: function ( key ) { - removeAll: function () { + delete objects[ key ]; - objects = {}; + }, - } + 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' }; - /** +} + +/*********************************/ +/********** EXTENSIONS ***********/ +/*********************************/ + +const EXTENSIONS = { + KHR_BINARY_GLTF: 'KHR_binary_glTF', + KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', + KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', + KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', + KHR_MATERIALS_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' +}; + +/** * Punctual Lights Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual */ - function GLTFLightsExtension( parser ) { +class GLTFLightsExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; @@ -436,18 +434,18 @@ var GLTFLoader = ( function () { } - GLTFLightsExtension.prototype._markDefs = function () { + _markDefs() { - var parser = this.parser; - var nodeDefs = this.parser.json.nodes || []; + const parser = this.parser; + const nodeDefs = this.parser.json.nodes || []; - for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - var nodeDef = nodeDefs[ nodeIndex ]; + const nodeDef = nodeDefs[ nodeIndex ]; if ( nodeDef.extensions - && nodeDef.extensions[ this.name ] - && nodeDef.extensions[ this.name ].light !== undefined ) { + && nodeDef.extensions[ this.name ] + && nodeDef.extensions[ this.name ].light !== undefined ) { parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); @@ -455,27 +453,27 @@ var GLTFLoader = ( function () { } - }; + } - GLTFLightsExtension.prototype._loadLight = function ( lightIndex ) { + _loadLight( lightIndex ) { - var parser = this.parser; - var cacheKey = 'light:' + lightIndex; - var dependency = parser.cache.get( cacheKey ); + const parser = this.parser; + const cacheKey = 'light:' + lightIndex; + let 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; + const json = parser.json; + const extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; + const lightDefs = extensions.lights || []; + const lightDef = lightDefs[ lightIndex ]; + let lightNode; - var color = new Color( 0xffffff ); + const color = new Color( 0xffffff ); if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); - var range = lightDef.range !== undefined ? lightDef.range : 0; + const range = lightDef.range !== undefined ? lightDef.range : 0; switch ( lightDef.type ) { @@ -524,16 +522,16 @@ var GLTFLoader = ( function () { return dependency; - }; + } - GLTFLightsExtension.prototype.createNodeAttachment = function ( nodeIndex ) { + createNodeAttachment( 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; + const self = this; + const parser = this.parser; + const json = parser.json; + const nodeDef = json.nodes[ nodeIndex ]; + const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; + const lightIndex = lightDef.light; if ( lightIndex === undefined ) return null; @@ -543,39 +541,43 @@ var GLTFLoader = ( function () { } ); - }; + } - /** +} + +/** * Unlit Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit */ - function GLTFMaterialsUnlitExtension() { +class GLTFMaterialsUnlitExtension { + + constructor() { this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; } - GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () { + getMaterialType() { return MeshBasicMaterial; - }; + } - GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) { + extendParams( materialParams, materialDef, parser ) { - var pending = []; + const pending = []; materialParams.color = new Color( 1.0, 1.0, 1.0 ); materialParams.opacity = 1.0; - var metallicRoughness = materialDef.pbrMetallicRoughness; + const metallicRoughness = materialDef.pbrMetallicRoughness; if ( metallicRoughness ) { if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - var array = metallicRoughness.baseColorFactor; + const array = metallicRoughness.baseColorFactor; materialParams.color.fromArray( array ); materialParams.opacity = array[ 3 ]; @@ -592,35 +594,39 @@ var GLTFLoader = ( function () { 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 ) { +class GLTFMaterialsClearcoatExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; } - GLTFMaterialsClearcoatExtension.prototype.getMaterialType = function ( materialIndex ) { + getMaterialType( materialIndex ) { - var parser = this.parser; - var materialDef = parser.json.materials[ materialIndex ]; + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; return MeshPhysicalMaterial; - }; + } - GLTFMaterialsClearcoatExtension.prototype.extendMaterialParams = function ( materialIndex, materialParams ) { + extendMaterialParams( materialIndex, materialParams ) { - var parser = this.parser; - var materialDef = parser.json.materials[ materialIndex ]; + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { @@ -628,9 +634,9 @@ var GLTFLoader = ( function () { } - var pending = []; + const pending = []; - var extension = materialDef.extensions[ this.name ]; + const extension = materialDef.extensions[ this.name ]; if ( extension.clearcoatFactor !== undefined ) { @@ -662,7 +668,7 @@ var GLTFLoader = ( function () { if ( extension.clearcoatNormalTexture.scale !== undefined ) { - var scale = extension.clearcoatNormalTexture.scale; + const scale = extension.clearcoatNormalTexture.scale; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 materialParams.clearcoatNormalScale = new Vector2( scale, - scale ); @@ -673,36 +679,40 @@ var GLTFLoader = ( function () { 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 ) { +class GLTFMaterialsTransmissionExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; } - GLTFMaterialsTransmissionExtension.prototype.getMaterialType = function ( materialIndex ) { + getMaterialType( materialIndex ) { - var parser = this.parser; - var materialDef = parser.json.materials[ materialIndex ]; + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; return MeshPhysicalMaterial; - }; + } - GLTFMaterialsTransmissionExtension.prototype.extendMaterialParams = function ( materialIndex, materialParams ) { + extendMaterialParams( materialIndex, materialParams ) { - var parser = this.parser; - var materialDef = parser.json.materials[ materialIndex ]; + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { @@ -710,9 +720,9 @@ var GLTFLoader = ( function () { } - var pending = []; + const pending = []; - var extension = materialDef.extensions[ this.name ]; + const extension = materialDef.extensions[ this.name ]; if ( extension.transmissionFactor !== undefined ) { @@ -728,26 +738,30 @@ var GLTFLoader = ( function () { 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 ) { +class GLTFTextureBasisUExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.KHR_TEXTURE_BASISU; } - GLTFTextureBasisUExtension.prototype.loadTexture = function ( textureIndex ) { + loadTexture( textureIndex ) { - var parser = this.parser; - var json = parser.json; + const parser = this.parser; + const json = parser.json; - var textureDef = json.textures[ textureIndex ]; + const textureDef = json.textures[ textureIndex ]; if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { @@ -755,9 +769,9 @@ var GLTFLoader = ( function () { } - var extension = textureDef.extensions[ this.name ]; - var source = json.images[ extension.source ]; - var loader = parser.options.ktx2Loader; + const extension = textureDef.extensions[ this.name ]; + const source = json.images[ extension.source ]; + const loader = parser.options.ktx2Loader; if ( ! loader ) { @@ -776,14 +790,18 @@ var GLTFLoader = ( function () { 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 ) { +class GLTFTextureWebPExtension { + + constructor( parser ) { this.parser = parser; this.name = EXTENSIONS.EXT_TEXTURE_WEBP; @@ -791,13 +809,13 @@ var GLTFLoader = ( function () { } - GLTFTextureWebPExtension.prototype.loadTexture = function ( textureIndex ) { + loadTexture( textureIndex ) { - var name = this.name; - var parser = this.parser; - var json = parser.json; + const name = this.name; + const parser = this.parser; + const json = parser.json; - var textureDef = json.textures[ textureIndex ]; + const textureDef = json.textures[ textureIndex ]; if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { @@ -805,13 +823,13 @@ var GLTFLoader = ( function () { } - var extension = textureDef.extensions[ name ]; - var source = json.images[ extension.source ]; + const extension = textureDef.extensions[ name ]; + const source = json.images[ extension.source ]; - var loader = parser.textureLoader; + let loader = parser.textureLoader; if ( source.uri ) { - var handler = parser.options.manager.getHandler( source.uri ); + const handler = parser.options.manager.getHandler( source.uri ); if ( handler !== null ) loader = handler; } @@ -831,15 +849,15 @@ var GLTFLoader = ( function () { } ); - }; + } - GLTFTextureWebPExtension.prototype.detectSupport = function () { + detectSupport() { if ( ! this.isSupported ) { this.isSupported = new Promise( function ( resolve ) { - var image = new Image(); + const image = new Image(); // Lossy test image. Support for lossy images doesn't guarantee support for all // WebP images, unfortunately. @@ -857,31 +875,35 @@ var GLTFLoader = ( function () { 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 ) { +class GLTFMeshoptCompression { + + constructor( parser ) { this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; this.parser = parser; } - GLTFMeshoptCompression.prototype.loadBufferView = function ( index ) { + loadBufferView( index ) { - var json = this.parser.json; - var bufferView = json.bufferViews[ index ]; + const json = this.parser.json; + const bufferView = json.bufferViews[ index ]; if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { - var extensionDef = bufferView.extensions[ this.name ]; + const extensionDef = bufferView.extensions[ this.name ]; - var buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); - var decoder = this.parser.options.meshoptDecoder; + const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); + const decoder = this.parser.options.meshoptDecoder; if ( ! decoder || ! decoder.supported ) { @@ -900,14 +922,14 @@ var GLTFLoader = ( function () { return Promise.all( [ buffer, decoder.ready ] ).then( function ( res ) { - var byteOffset = extensionDef.byteOffset || 0; - var byteLength = extensionDef.byteLength || 0; + const byteOffset = extensionDef.byteOffset || 0; + const byteLength = extensionDef.byteLength || 0; - var count = extensionDef.count; - var stride = extensionDef.byteStride; + const count = extensionDef.count; + const stride = extensionDef.byteStride; - var result = new ArrayBuffer( count * stride ); - var source = new Uint8Array( res[ 0 ], byteOffset, byteLength ); + const result = new ArrayBuffer( count * stride ); + const source = new Uint8Array( res[ 0 ], byteOffset, byteLength ); decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); return result; @@ -920,20 +942,24 @@ var GLTFLoader = ( function () { } - }; + } - /* 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 ) { +/* BINARY EXTENSION */ +const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; +const BINARY_EXTENSION_HEADER_LENGTH = 12; +const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; + +class GLTFBinaryExtension { + + constructor( data ) { this.name = EXTENSIONS.KHR_BINARY_GLTF; this.content = null; this.body = null; - var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); + const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); this.header = { magic: LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ), @@ -951,26 +977,26 @@ var GLTFLoader = ( function () { } - var chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; - var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); - var chunkIndex = 0; + const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; + const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); + let chunkIndex = 0; while ( chunkIndex < chunkContentsLength ) { - var chunkLength = chunkView.getUint32( chunkIndex, true ); + const chunkLength = chunkView.getUint32( chunkIndex, true ); chunkIndex += 4; - var chunkType = chunkView.getUint32( chunkIndex, true ); + const 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 ); + const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); this.content = LoaderUtils.decodeText( contentArray ); } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { - var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; + const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; this.body = data.slice( byteOffset, byteOffset + chunkLength ); } @@ -989,12 +1015,16 @@ var GLTFLoader = ( function () { } - /** +} + +/** * DRACO Mesh Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression */ - function GLTFDracoMeshCompressionExtension( json, dracoLoader ) { +class GLTFDracoMeshCompressionExtension { + + constructor( json, dracoLoader ) { if ( ! dracoLoader ) { @@ -1009,32 +1039,32 @@ var GLTFLoader = ( function () { } - GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { + decodePrimitive( 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 = {}; + const json = this.json; + const dracoLoader = this.dracoLoader; + const bufferViewIndex = primitive.extensions[ this.name ].bufferView; + const gltfAttributeMap = primitive.extensions[ this.name ].attributes; + const threeAttributeMap = {}; + const attributeNormalizedMap = {}; + const attributeTypeMap = {}; - for ( var attributeName in gltfAttributeMap ) { + for ( const attributeName in gltfAttributeMap ) { - var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; } - for ( attributeName in primitive.attributes ) { + for ( const attributeName in primitive.attributes ) { - var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); if ( gltfAttributeMap[ attributeName ] !== undefined ) { - var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; - var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; + const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; attributeTypeMap[ threeAttributeName ] = componentType; attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; @@ -1049,10 +1079,10 @@ var GLTFLoader = ( function () { dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { - for ( var attributeName in geometry.attributes ) { + for ( const attributeName in geometry.attributes ) { - var attribute = geometry.attributes[ attributeName ]; - var normalized = attributeNormalizedMap[ attributeName ]; + const attribute = geometry.attributes[ attributeName ]; + const normalized = attributeNormalizedMap[ attributeName ]; if ( normalized !== undefined ) attribute.normalized = normalized; @@ -1066,20 +1096,24 @@ var GLTFLoader = ( function () { } ); - }; + } - /** +} + +/** * Texture Transform Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform */ - function GLTFTextureTransformExtension() { +class GLTFTextureTransformExtension { + + constructor() { this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; } - GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) { + extendTexture( texture, transform ) { texture = texture.clone(); @@ -1111,40 +1145,44 @@ var GLTFLoader = ( function () { return texture; - }; + } - /** +} + +/** * Specular-Glossiness Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness */ - /** +/** * A sub class of StandardMaterial with some of the functionality * changed via the `onBeforeCompile` callback * @pailhead */ - function GLTFMeshStandardSGMaterial( params ) { +class GLTFMeshStandardSGMaterial extends MeshStandardMaterial { + + constructor( params ) { - MeshStandardMaterial.call( this ); + super(); this.isGLTFSpecularGlossinessMaterial = true; //various chunks that need replacing - var specularMapParsFragmentChunk = [ + const specularMapParsFragmentChunk = [ '#ifdef USE_SPECULARMAP', ' uniform sampler2D specularMap;', '#endif' ].join( '\n' ); - var glossinessMapParsFragmentChunk = [ + const glossinessMapParsFragmentChunk = [ '#ifdef USE_GLOSSINESSMAP', ' uniform sampler2D glossinessMap;', '#endif' ].join( '\n' ); - var specularMapFragmentChunk = [ + const specularMapFragmentChunk = [ 'vec3 specularFactor = specular;', '#ifdef USE_SPECULARMAP', ' vec4 texelSpecular = texture2D( specularMap, vUv );', @@ -1154,7 +1192,7 @@ var GLTFLoader = ( function () { '#endif' ].join( '\n' ); - var glossinessMapFragmentChunk = [ + const glossinessMapFragmentChunk = [ 'float glossinessFactor = glossiness;', '#ifdef USE_GLOSSINESSMAP', ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', @@ -1163,7 +1201,7 @@ var GLTFLoader = ( function () { '#endif' ].join( '\n' ); - var lightPhysicalFragmentChunk = [ + const 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 ) ) );', @@ -1174,7 +1212,7 @@ var GLTFLoader = ( function () { 'material.specularColor = specularFactor;', ].join( '\n' ); - var uniforms = { + const uniforms = { specular: { value: new Color().setHex( 0xffffff ) }, glossiness: { value: 1 }, specularMap: { value: null }, @@ -1185,7 +1223,7 @@ var GLTFLoader = ( function () { this.onBeforeCompile = function ( shader ) { - for ( var uniformName in uniforms ) { + for ( const uniformName in uniforms ) { shader.uniforms[ uniformName ] = uniforms[ uniformName ]; @@ -1289,12 +1327,10 @@ var GLTFLoader = ( function () { } - GLTFMeshStandardSGMaterial.prototype = Object.create( MeshStandardMaterial.prototype ); - GLTFMeshStandardSGMaterial.prototype.constructor = GLTFMeshStandardSGMaterial; + copy( source ) { - GLTFMeshStandardSGMaterial.prototype.copy = function ( source ) { + super.copy( source ); - MeshStandardMaterial.prototype.copy.call( this, source ); this.specularMap = source.specularMap; this.specular.copy( source.specular ); this.glossinessMap = source.glossinessMap; @@ -1305,182 +1341,188 @@ var GLTFLoader = ( function () { 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; +} + + +class GLTFMaterialsPbrSpecularGlossinessExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS; + + this.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', + ]; - }, + } - extendParams: function ( materialParams, materialDef, parser ) { + getMaterialType() { - var pbrSpecularGlossiness = materialDef.extensions[ this.name ]; + return GLTFMeshStandardSGMaterial; - materialParams.color = new Color( 1.0, 1.0, 1.0 ); - materialParams.opacity = 1.0; + } - var pending = []; + extendParams( materialParams, materialDef, parser ) { - if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + const pbrSpecularGlossiness = materialDef.extensions[ this.name ]; - var array = pbrSpecularGlossiness.diffuseFactor; + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; - materialParams.color.fromArray( array ); - materialParams.opacity = array[ 3 ]; + const pending = []; - } + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { - if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + const array = pbrSpecularGlossiness.diffuseFactor; - pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; - } + } - materialParams.emissive = new Color( 0.0, 0.0, 0.0 ); - materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; - materialParams.specular = new Color( 1.0, 1.0, 1.0 ); + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { - if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { + pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); - materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + } - } + materialParams.emissive = new Color( 0.0, 0.0, 0.0 ); + materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; + materialParams.specular = new Color( 1.0, 1.0, 1.0 ); - if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { - var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; - pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); - pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); + materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); - } + } - return Promise.all( pending ); + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { - }, + const specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; + pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); + pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); - createMaterial: function ( materialParams ) { + } - var material = new GLTFMeshStandardSGMaterial( materialParams ); - material.fog = true; + return Promise.all( pending ); - material.color = materialParams.color; + } - material.map = materialParams.map === undefined ? null : materialParams.map; + createMaterial( materialParams ) { - material.lightMap = null; - material.lightMapIntensity = 1.0; + const material = new GLTFMeshStandardSGMaterial( materialParams ); + material.fog = true; - material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap; - material.aoMapIntensity = 1.0; + material.color = materialParams.color; - material.emissive = materialParams.emissive; - material.emissiveIntensity = 1.0; - material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap; + material.map = materialParams.map === undefined ? null : materialParams.map; - material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap; - material.bumpScale = 1; + material.lightMap = null; + material.lightMapIntensity = 1.0; - material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap; - material.normalMapType = TangentSpaceNormalMap; + material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap; + material.aoMapIntensity = 1.0; - if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale; + material.emissive = materialParams.emissive; + material.emissiveIntensity = 1.0; + material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap; - material.displacementMap = null; - material.displacementScale = 1; - material.displacementBias = 0; + material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap; + material.bumpScale = 1; - material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap; - material.specular = materialParams.specular; + material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap; + material.normalMapType = TangentSpaceNormalMap; - material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap; - material.glossiness = materialParams.glossiness; + if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale; - material.alphaMap = null; + material.displacementMap = null; + material.displacementScale = 1; + material.displacementBias = 0; - material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap; - material.envMapIntensity = 1.0; + material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap; + material.specular = materialParams.specular; - material.refractionRatio = 0.98; + material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap; + material.glossiness = materialParams.glossiness; - return material; + 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() { +class GLTFMeshQuantizationExtension { + + constructor() { this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; } - /*********************************/ - /********** INTERPOLATION ********/ - /*********************************/ +} - // Spline Interpolation - // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation - function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { +/*********************************/ +/********** INTERPOLATION ********/ +/*********************************/ - Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); +// Spline Interpolation +// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation +class GLTFCubicSplineInterpolant extends Interpolant { - } + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - GLTFCubicSplineInterpolant.prototype = Object.create( Interpolant.prototype ); - GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant; + } - GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) { + copySampleValue_( index ) { // Copies a sample value to the result buffer. See description of glTF // CUBICSPLINE values layout in interpolate_() function below. - var result = this.resultBuffer, + const result = this.resultBuffer, values = this.sampleValues, valueSize = this.valueSize, offset = index * valueSize * 3 + valueSize; - for ( var i = 0; i !== valueSize; i ++ ) { + for ( let i = 0; i !== valueSize; i ++ ) { result[ i ] = values[ offset + i ]; @@ -1488,236 +1530,238 @@ var GLTFLoader = ( function () { 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 ++ ) { +GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; - 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) +GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; - result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; +GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { - } + const result = this.resultBuffer; + const values = this.sampleValues; + const stride = this.valueSize; - return result; + const stride2 = stride * 2; + const stride3 = stride * 3; - }; + const td = t1 - t0; - /*********************************/ - /********** 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 - }; + const p = ( t - t0 ) / td; + const pp = p * p; + const ppp = pp * p; - var WEBGL_COMPONENT_TYPES = { - 5120: Int8Array, - 5121: Uint8Array, - 5122: Int16Array, - 5123: Uint16Array, - 5125: Uint32Array, - 5126: Float32Array - }; + const offset1 = i1 * stride3; + const offset0 = offset1 - stride3; - var WEBGL_FILTERS = { - 9728: NearestFilter, - 9729: LinearFilter, - 9984: NearestMipmapNearestFilter, - 9985: LinearMipmapNearestFilter, - 9986: NearestMipmapLinearFilter, - 9987: LinearMipmapLinearFilter - }; + const s2 = - 2 * ppp + 3 * pp; + const s3 = ppp - pp; + const s0 = 1 - s2; + const s1 = s3 - pp + p; - var WEBGL_WRAPPINGS = { - 33071: ClampToEdgeWrapping, - 33648: MirroredRepeatWrapping, - 10497: RepeatWrapping - }; + // Layout of keyframe output values for CUBICSPLINE animations: + // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] + for ( let i = 0; i !== stride; i ++ ) { - var WEBGL_TYPE_SIZES = { - 'SCALAR': 1, - 'VEC2': 2, - 'VEC3': 3, - 'VEC4': 4, - 'MAT2': 4, - 'MAT3': 9, - 'MAT4': 16 - }; + const p0 = values[ offset0 + i + stride ]; // splineVertex_k + const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) + const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 + const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) - var ATTRIBUTES = { - POSITION: 'position', - NORMAL: 'normal', - TANGENT: 'tangent', - TEXCOORD_0: 'uv', - TEXCOORD_1: 'uv2', - COLOR_0: 'color', - WEIGHTS_0: 'skinWeight', - JOINTS_0: 'skinIndex', - }; + result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; - 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 + return result; + +}; + +/*********************************/ +/********** INTERNALS ************/ +/*********************************/ + +/* CONSTANTS */ + +const WEBGL_CONSTANTS = { + FLOAT: 5126, + //FLOAT_MAT2: 35674, + FLOAT_MAT3: 35675, + FLOAT_MAT4: 35676, + FLOAT_VEC2: 35664, + FLOAT_VEC3: 35665, + FLOAT_VEC4: 35666, + LINEAR: 9729, + REPEAT: 10497, + SAMPLER_2D: 35678, + POINTS: 0, + LINES: 1, + LINE_LOOP: 2, + LINE_STRIP: 3, + TRIANGLES: 4, + TRIANGLE_STRIP: 5, + TRIANGLE_FAN: 6, + UNSIGNED_BYTE: 5121, + UNSIGNED_SHORT: 5123 +}; + +const WEBGL_COMPONENT_TYPES = { + 5120: Int8Array, + 5121: Uint8Array, + 5122: Int16Array, + 5123: Uint16Array, + 5125: Uint32Array, + 5126: Float32Array +}; + +const WEBGL_FILTERS = { + 9728: NearestFilter, + 9729: LinearFilter, + 9984: NearestMipmapNearestFilter, + 9985: LinearMipmapNearestFilter, + 9986: NearestMipmapLinearFilter, + 9987: LinearMipmapLinearFilter +}; + +const WEBGL_WRAPPINGS = { + 33071: ClampToEdgeWrapping, + 33648: MirroredRepeatWrapping, + 10497: RepeatWrapping +}; + +const WEBGL_TYPE_SIZES = { + 'SCALAR': 1, + 'VEC2': 2, + 'VEC3': 3, + 'VEC4': 4, + 'MAT2': 4, + 'MAT3': 9, + 'MAT4': 16 +}; + +const ATTRIBUTES = { + POSITION: 'position', + NORMAL: 'normal', + TANGENT: 'tangent', + TEXCOORD_0: 'uv', + TEXCOORD_1: 'uv2', + COLOR_0: 'color', + WEIGHTS_0: 'skinWeight', + JOINTS_0: 'skinIndex', +}; + +const PATH_PROPERTIES = { + scale: 'scale', + translation: 'position', + rotation: 'quaternion', + weights: 'morphTargetInfluences' +}; + +const INTERPOLATION = { + CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each // keyframe track will be initialized with a default interpolation type, then modified. - LINEAR: InterpolateLinear, - STEP: InterpolateDiscrete - }; + LINEAR: InterpolateLinear, + STEP: InterpolateDiscrete +}; - var ALPHA_MODES = { - OPAQUE: 'OPAQUE', - MASK: 'MASK', - BLEND: 'BLEND' - }; +const ALPHA_MODES = { + OPAQUE: 'OPAQUE', + MASK: 'MASK', + BLEND: 'BLEND' +}; - /* UTILITY FUNCTIONS */ +/* UTILITY FUNCTIONS */ - function resolveURL( url, path ) { +function resolveURL( url, path ) { - // Invalid URL - if ( typeof url !== 'string' || url === '' ) return ''; + // Invalid URL + if ( typeof url !== 'string' || url === '' ) return ''; - // Host Relative URL - if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { + // Host Relative URL + if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { - path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); + path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); - } + } - // Absolute URL http://,https://,// - if ( /^(https?:)?\/\//i.test( url ) ) return url; + // Absolute URL http://,https://,// + if ( /^(https?:)?\/\//i.test( url ) ) return url; - // Data URI - if ( /^data:.*,.*$/i.test( url ) ) return url; + // Data URI + if ( /^data:.*,.*$/i.test( url ) ) return url; - // Blob URL - if ( /^blob:.*$/i.test( url ) ) return url; + // Blob URL + if ( /^blob:.*$/i.test( url ) ) return url; - // Relative URL - return path + 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 MeshStandardMaterial( { - color: 0xFFFFFF, - emissive: 0x000000, - metalness: 1, - roughness: 1, - transparent: false, - depthTest: true, - side: FrontSide - } ); - - } - - return cache[ 'DefaultMaterial' ]; +function createDefaultMaterial( cache ) { + + if ( cache[ 'DefaultMaterial' ] === undefined ) { + + cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( { + color: 0xFFFFFF, + emissive: 0x000000, + metalness: 1, + roughness: 1, + transparent: false, + depthTest: true, + side: FrontSide + } ); } - function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { + return cache[ 'DefaultMaterial' ]; - // Add unknown glTF extensions to an object's userData. +} - for ( var name in objectDef.extensions ) { +function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { - if ( knownExtensions[ name ] === undefined ) { + // Add unknown glTF extensions to an object's userData. - object.userData.gltfExtensions = object.userData.gltfExtensions || {}; - object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; + for ( const name in objectDef.extensions ) { - } + if ( knownExtensions[ name ] === undefined ) { + + object.userData.gltfExtensions = object.userData.gltfExtensions || {}; + object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; } } - /** +} + +/** * @param {Object3D|Material|BufferGeometry} object * @param {GLTF.definition} gltfDef */ - function assignExtrasToUserData( object, gltfDef ) { - - if ( gltfDef.extras !== undefined ) { +function assignExtrasToUserData( object, gltfDef ) { - if ( typeof gltfDef.extras === 'object' ) { + if ( gltfDef.extras !== undefined ) { - Object.assign( object.userData, gltfDef.extras ); + if ( typeof gltfDef.extras === 'object' ) { - } else { + Object.assign( object.userData, gltfDef.extras ); - console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); + } else { - } + console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); } } - /** +} + +/** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets * * @param {BufferGeometry} geometry @@ -1725,185 +1769,187 @@ var GLTFLoader = ( function () { * @param {GLTFParser} parser * @return {Promise} */ - function addMorphTargets( geometry, targets, parser ) { - - var hasMorphPosition = false; - var hasMorphNormal = false; +function addMorphTargets( geometry, targets, parser ) { - for ( var i = 0, il = targets.length; i < il; i ++ ) { + let hasMorphPosition = false; + let hasMorphNormal = false; - var target = targets[ i ]; + for ( let i = 0, il = targets.length; i < il; i ++ ) { - if ( target.POSITION !== undefined ) hasMorphPosition = true; - if ( target.NORMAL !== undefined ) hasMorphNormal = true; + const target = targets[ i ]; - if ( hasMorphPosition && hasMorphNormal ) break; + 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 = []; + if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry ); - for ( var i = 0, il = targets.length; i < il; i ++ ) { + const pendingPositionAccessors = []; + const pendingNormalAccessors = []; - var target = targets[ i ]; + for ( let i = 0, il = targets.length; i < il; i ++ ) { - if ( hasMorphPosition ) { + const target = targets[ i ]; - var pendingAccessor = target.POSITION !== undefined - ? parser.getDependency( 'accessor', target.POSITION ) - : geometry.attributes.position; + if ( hasMorphPosition ) { - pendingPositionAccessors.push( pendingAccessor ); + const 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; + if ( hasMorphNormal ) { - pendingNormalAccessors.push( pendingAccessor ); + const 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 ]; + return Promise.all( [ + Promise.all( pendingPositionAccessors ), + Promise.all( pendingNormalAccessors ) + ] ).then( function ( accessors ) { - if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; - if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; - geometry.morphTargetsRelative = true; + const morphPositions = accessors[ 0 ]; + const morphNormals = accessors[ 1 ]; - return geometry; + if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; + if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; + geometry.morphTargetsRelative = true; - } ); + return geometry; - } + } ); - /** +} + +/** * @param {Mesh} mesh * @param {GLTF.Mesh} meshDef */ - function updateMorphTargets( mesh, meshDef ) { - - mesh.updateMorphTargets(); +function updateMorphTargets( mesh, meshDef ) { - if ( meshDef.weights !== undefined ) { + mesh.updateMorphTargets(); - for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { + if ( meshDef.weights !== undefined ) { - mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; + for ( let i = 0, il = meshDef.weights.length; i < il; i ++ ) { - } + mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; } - // .extras has user-defined data, so check that .extras.targetNames is an array. - if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { + } - var targetNames = meshDef.extras.targetNames; + // .extras has user-defined data, so check that .extras.targetNames is an array. + if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { - if ( mesh.morphTargetInfluences.length === targetNames.length ) { + const targetNames = meshDef.extras.targetNames; - mesh.morphTargetDictionary = {}; + if ( mesh.morphTargetInfluences.length === targetNames.length ) { - for ( var i = 0, il = targetNames.length; i < il; i ++ ) { + mesh.morphTargetDictionary = {}; - mesh.morphTargetDictionary[ targetNames[ i ] ] = i; + for ( let i = 0, il = targetNames.length; i < il; i ++ ) { - } + mesh.morphTargetDictionary[ targetNames[ i ] ] = i; - } else { + } - console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); + } else { - } + console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); } } - function createPrimitiveKey( primitiveDef ) { +} + +function createPrimitiveKey( primitiveDef ) { - var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; - var geometryKey; + const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; + let geometryKey; - if ( dracoExtension ) { + if ( dracoExtension ) { - geometryKey = 'draco:' + dracoExtension.bufferView + geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices + ':' + createAttributesKey( dracoExtension.attributes ); - } else { - - geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; - - } + } else { - return geometryKey; + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; } - function createAttributesKey( attributes ) { + return geometryKey; - var attributesKey = ''; +} - var keys = Object.keys( attributes ).sort(); +function createAttributesKey( attributes ) { - for ( var i = 0, il = keys.length; i < il; i ++ ) { + let attributesKey = ''; - attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; + const keys = Object.keys( attributes ).sort(); - } + for ( let i = 0, il = keys.length; i < il; i ++ ) { - return attributesKey; + attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; } - function getNormalizedComponentScale( constructor ) { + return attributesKey; - // Reference: - // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data +} - switch ( constructor ) { +function getNormalizedComponentScale( constructor ) { - case Int8Array: - return 1 / 127; + // Reference: + // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data - case Uint8Array: - return 1 / 255; + switch ( constructor ) { - case Int16Array: - return 1 / 32767; + case Int8Array: + return 1 / 127; - case Uint16Array: - return 1 / 65535; + case Uint8Array: + return 1 / 255; - default: - throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); + case Int16Array: + return 1 / 32767; - } + case Uint16Array: + return 1 / 65535; + + default: + throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); } - /* GLTF PARSER */ +} - function GLTFParser( json, options ) { +/* GLTF PARSER */ - this.json = json || {}; +class GLTFParser { + + constructor( json = {}, options = {} ) { + + this.json = json; this.extensions = {}; this.plugins = {}; - this.options = options || {}; + this.options = options; // loader object cache this.cache = new GLTFRegistry(); @@ -1948,23 +1994,23 @@ var GLTFLoader = ( function () { } - GLTFParser.prototype.setExtensions = function ( extensions ) { + setExtensions( extensions ) { this.extensions = extensions; - }; + } - GLTFParser.prototype.setPlugins = function ( plugins ) { + setPlugins( plugins ) { this.plugins = plugins; - }; + } - GLTFParser.prototype.parse = function ( onLoad, onError ) { + parse( onLoad, onError ) { - var parser = this; - var json = this.json; - var extensions = this.extensions; + const parser = this; + const json = this.json; + const extensions = this.extensions; // Clear the loader cache this.cache.removeAll(); @@ -1992,7 +2038,7 @@ var GLTFLoader = ( function () { } ).then( function ( dependencies ) { - var result = { + const result = { scene: dependencies[ 0 ][ json.scene || 0 ], scenes: dependencies[ 0 ], animations: dependencies[ 1 ], @@ -2018,24 +2064,24 @@ var GLTFLoader = ( function () { } ).catch( onError ); - }; + } /** * Marks the special nodes/meshes in json for efficient parse. */ - GLTFParser.prototype._markDefs = function () { + _markDefs() { - var nodeDefs = this.json.nodes || []; - var skinDefs = this.json.skins || []; - var meshDefs = this.json.meshes || []; + const nodeDefs = this.json.nodes || []; + const skinDefs = this.json.skins || []; + const meshDefs = this.json.meshes || []; // Nothing in the node definition indicates whether it is a Bone or an // Object3D. Use the skins' joint references to mark bones. - for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { + for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { - var joints = skinDefs[ skinIndex ].joints; + const joints = skinDefs[ skinIndex ].joints; - for ( var i = 0, il = joints.length; i < il; i ++ ) { + for ( let i = 0, il = joints.length; i < il; i ++ ) { nodeDefs[ joints[ i ] ].isBone = true; @@ -2045,9 +2091,9 @@ var GLTFLoader = ( function () { // Iterate over all nodes, marking references to shared resources, // as well as skeleton joints. - for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { - var nodeDef = nodeDefs[ nodeIndex ]; + const nodeDef = nodeDefs[ nodeIndex ]; if ( nodeDef.mesh !== undefined ) { @@ -2072,7 +2118,7 @@ var GLTFLoader = ( function () { } - }; + } /** * Counts references to shared node / Object3D resources. These resources @@ -2083,7 +2129,7 @@ var GLTFLoader = ( function () { * * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. */ - GLTFParser.prototype._addNodeRef = function ( cache, index ) { + _addNodeRef( cache, index ) { if ( index === undefined ) return; @@ -2095,29 +2141,29 @@ var GLTFLoader = ( function () { cache.refs[ index ] ++; - }; + } /** Returns a reference to a shared resource, cloning it if necessary. */ - GLTFParser.prototype._getNodeRef = function ( cache, index, object ) { + _getNodeRef( cache, index, object ) { if ( cache.refs[ index ] <= 1 ) return object; - var ref = object.clone(); + const ref = object.clone(); ref.name += '_instance_' + ( cache.uses[ index ] ++ ); return ref; - }; + } - GLTFParser.prototype._invokeOne = function ( func ) { + _invokeOne( func ) { - var extensions = Object.values( this.plugins ); + const extensions = Object.values( this.plugins ); extensions.push( this ); - for ( var i = 0; i < extensions.length; i ++ ) { + for ( let i = 0; i < extensions.length; i ++ ) { - var result = func( extensions[ i ] ); + const result = func( extensions[ i ] ); if ( result ) return result; @@ -2125,18 +2171,18 @@ var GLTFLoader = ( function () { return null; - }; + } - GLTFParser.prototype._invokeAll = function ( func ) { + _invokeAll( func ) { - var extensions = Object.values( this.plugins ); + const extensions = Object.values( this.plugins ); extensions.unshift( this ); - var pending = []; + const pending = []; - for ( var i = 0; i < extensions.length; i ++ ) { + for ( let i = 0; i < extensions.length; i ++ ) { - var result = func( extensions[ i ] ); + const result = func( extensions[ i ] ); if ( result ) pending.push( result ); @@ -2144,7 +2190,7 @@ var GLTFLoader = ( function () { return pending; - }; + } /** * Requests the specified dependency asynchronously, with caching. @@ -2152,10 +2198,10 @@ var GLTFLoader = ( function () { * @param {number} index * @return {Promise} */ - GLTFParser.prototype.getDependency = function ( type, index ) { + getDependency( type, index ) { - var cacheKey = type + ':' + index; - var dependency = this.cache.get( cacheKey ); + const cacheKey = type + ':' + index; + let dependency = this.cache.get( cacheKey ); if ( ! dependency ) { @@ -2232,21 +2278,21 @@ var GLTFLoader = ( function () { return dependency; - }; + } /** * Requests all dependencies of the specified type asynchronously, with caching. * @param {string} type * @return {Promise>} */ - GLTFParser.prototype.getDependencies = function ( type ) { + getDependencies( type ) { - var dependencies = this.cache.get( type ); + let dependencies = this.cache.get( type ); if ( ! dependencies ) { - var parser = this; - var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; + const parser = this; + const defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; dependencies = Promise.all( defs.map( function ( def, index ) { @@ -2260,17 +2306,17 @@ var GLTFLoader = ( function () { 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 ) { + loadBuffer( bufferIndex ) { - var bufferDef = this.json.buffers[ bufferIndex ]; - var loader = this.fileLoader; + const bufferDef = this.json.buffers[ bufferIndex ]; + const loader = this.fileLoader; if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { @@ -2285,7 +2331,7 @@ var GLTFLoader = ( function () { } - var options = this.options; + const options = this.options; return new Promise( function ( resolve, reject ) { @@ -2297,38 +2343,38 @@ var GLTFLoader = ( function () { } ); - }; + } /** * 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 ) { + loadBufferView( bufferViewIndex ) { - var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; + const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { - var byteLength = bufferViewDef.byteLength || 0; - var byteOffset = bufferViewDef.byteOffset || 0; + const byteLength = bufferViewDef.byteLength || 0; + const byteOffset = bufferViewDef.byteOffset || 0; return buffer.slice( byteOffset, byteOffset + byteLength ); } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors * @param {number} accessorIndex * @return {Promise} */ - GLTFParser.prototype.loadAccessor = function ( accessorIndex ) { + loadAccessor( accessorIndex ) { - var parser = this; - var json = this.json; + const parser = this; + const json = this.json; - var accessorDef = this.json.accessors[ accessorIndex ]; + const accessorDef = this.json.accessors[ accessorIndex ]; if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { @@ -2339,7 +2385,7 @@ var GLTFLoader = ( function () { } - var pendingBufferViews = []; + const pendingBufferViews = []; if ( accessorDef.bufferView !== undefined ) { @@ -2360,27 +2406,27 @@ var GLTFLoader = ( function () { return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { - var bufferView = bufferViews[ 0 ]; + const bufferView = bufferViews[ 0 ]; - var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; - var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. - 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; + const elementBytes = TypedArray.BYTES_PER_ELEMENT; + const itemBytes = elementBytes * itemSize; + const byteOffset = accessorDef.byteOffset || 0; + const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; + const normalized = accessorDef.normalized === true; + let array, bufferAttribute; // The buffer is not interleaved if the stride is the item size in bytes. if ( byteStride && byteStride !== itemBytes ) { // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer // This makes sure that IBA.count reflects accessor.count properly - var ibSlice = Math.floor( byteOffset / byteStride ); - var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; - var ib = parser.cache.get( ibCacheKey ); + const ibSlice = Math.floor( byteOffset / byteStride ); + const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; + let ib = parser.cache.get( ibCacheKey ); if ( ! ib ) { @@ -2414,14 +2460,14 @@ var GLTFLoader = ( function () { // 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 ]; + const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; - var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; - var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; + const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; + const 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 ); + const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); + const sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); if ( bufferView !== null ) { @@ -2430,9 +2476,9 @@ var GLTFLoader = ( function () { } - for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) { + for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { - var index = sparseIndices[ i ]; + const index = sparseIndices[ i ]; bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); @@ -2448,46 +2494,46 @@ var GLTFLoader = ( function () { } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures * @param {number} textureIndex * @return {Promise} */ - GLTFParser.prototype.loadTexture = function ( textureIndex ) { + loadTexture( textureIndex ) { - var json = this.json; - var options = this.options; - var textureDef = json.textures[ textureIndex ]; - var source = json.images[ textureDef.source ]; + const json = this.json; + const options = this.options; + const textureDef = json.textures[ textureIndex ]; + const source = json.images[ textureDef.source ]; - var loader = this.textureLoader; + let loader = this.textureLoader; if ( source.uri ) { - var handler = options.manager.getHandler( source.uri ); + const handler = options.manager.getHandler( source.uri ); if ( handler !== null ) loader = handler; } return this.loadTextureImage( textureIndex, source, loader ); - }; + } - GLTFParser.prototype.loadTextureImage = function ( textureIndex, source, loader ) { + loadTextureImage( textureIndex, source, loader ) { - var parser = this; - var json = this.json; - var options = this.options; + const parser = this; + const json = this.json; + const options = this.options; - var textureDef = json.textures[ textureIndex ]; + const textureDef = json.textures[ textureIndex ]; - var URL = self.URL || self.webkitURL; + const URL = self.URL || self.webkitURL; - var sourceURI = source.uri; - var isObjectURL = false; - var hasAlpha = true; + let sourceURI = source.uri; + let isObjectURL = false; + let hasAlpha = true; if ( source.mimeType === 'image/jpeg' ) hasAlpha = false; @@ -2505,13 +2551,13 @@ var GLTFLoader = ( function () { // sometimes contains alpha. // // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header - var colorType = new DataView( bufferView, 25, 1 ).getUint8( 0, false ); + const 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 } ); + const blob = new Blob( [ bufferView ], { type: source.mimeType } ); sourceURI = URL.createObjectURL( blob ); return sourceURI; @@ -2527,7 +2573,7 @@ var GLTFLoader = ( function () { return new Promise( function ( resolve, reject ) { - var onLoad = resolve; + let onLoad = resolve; if ( loader.isImageBitmapLoader === true ) { @@ -2560,8 +2606,8 @@ var GLTFLoader = ( function () { // When there is definitely no alpha channel in the texture, set RGBFormat to save space. if ( ! hasAlpha ) texture.format = RGBFormat; - var samplers = json.samplers || {}; - var sampler = samplers[ textureDef.sampler ] || {}; + const samplers = json.samplers || {}; + const sampler = samplers[ textureDef.sampler ] || {}; texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter; texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; @@ -2577,7 +2623,7 @@ var GLTFLoader = ( function () { } ); - }; + } /** * Asynchronously assigns a texture to the given material parameters. @@ -2586,9 +2632,9 @@ var GLTFLoader = ( function () { * @param {Object} mapDef * @return {Promise} */ - GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) { + assignTexture( materialParams, mapName, mapDef ) { - var parser = this; + const parser = this; return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { @@ -2602,11 +2648,11 @@ var GLTFLoader = ( function () { if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { - var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; + const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; if ( transform ) { - var gltfReference = parser.associations.get( texture ); + const gltfReference = parser.associations.get( texture ); texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); parser.associations.set( texture, gltfReference ); @@ -2618,7 +2664,7 @@ var GLTFLoader = ( function () { } ); - }; + } /** * Assigns final material to a Mesh, Line, or Points instance. The instance @@ -2628,23 +2674,23 @@ var GLTFLoader = ( function () { * be created if necessary, and reused from a cache. * @param {Object3D} mesh Mesh, Line, or Points instance. */ - GLTFParser.prototype.assignFinalMaterial = function ( mesh ) { + assignFinalMaterial( mesh ) { - var geometry = mesh.geometry; - var material = mesh.material; + const geometry = mesh.geometry; + let 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; + const useVertexTangents = geometry.attributes.tangent !== undefined; + const useVertexColors = geometry.attributes.color !== undefined; + const useFlatShading = geometry.attributes.normal === undefined; + const useSkinning = mesh.isSkinnedMesh === true; + const useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0; + const useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined; if ( mesh.isPoints ) { - var cacheKey = 'PointsMaterial:' + material.uuid; + const cacheKey = 'PointsMaterial:' + material.uuid; - var pointsMaterial = this.cache.get( cacheKey ); + let pointsMaterial = this.cache.get( cacheKey ); if ( ! pointsMaterial ) { @@ -2662,9 +2708,9 @@ var GLTFLoader = ( function () { } else if ( mesh.isLine ) { - var cacheKey = 'LineBasicMaterial:' + material.uuid; + const cacheKey = 'LineBasicMaterial:' + material.uuid; - var lineMaterial = this.cache.get( cacheKey ); + let lineMaterial = this.cache.get( cacheKey ); if ( ! lineMaterial ) { @@ -2683,7 +2729,7 @@ var GLTFLoader = ( function () { // Clone the material if it will be modified if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { - var cacheKey = 'ClonedMaterial:' + material.uuid + ':'; + let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; if ( useSkinning ) cacheKey += 'skinning:'; @@ -2693,7 +2739,7 @@ var GLTFLoader = ( function () { if ( useMorphTargets ) cacheKey += 'morph-targets:'; if ( useMorphNormals ) cacheKey += 'morph-normals:'; - var cachedMaterial = this.cache.get( cacheKey ); + let cachedMaterial = this.cache.get( cacheKey ); if ( ! cachedMaterial ) { @@ -2735,41 +2781,41 @@ var GLTFLoader = ( function () { mesh.material = material; - }; + } - GLTFParser.prototype.getMaterialType = function ( /* materialIndex */ ) { + getMaterialType( /* materialIndex */ ) { return 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 ) { + loadMaterial( materialIndex ) { - var parser = this; - var json = this.json; - var extensions = this.extensions; - var materialDef = json.materials[ materialIndex ]; + const parser = this; + const json = this.json; + const extensions = this.extensions; + const materialDef = json.materials[ materialIndex ]; - var materialType; - var materialParams = {}; - var materialExtensions = materialDef.extensions || {}; + let materialType; + const materialParams = {}; + const materialExtensions = materialDef.extensions || {}; - var pending = []; + const pending = []; if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { - var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; + const 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 ]; + const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; materialType = kmuExtension.getMaterialType(); pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); @@ -2778,14 +2824,14 @@ var GLTFLoader = ( function () { // Specification: // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material - var metallicRoughness = materialDef.pbrMetallicRoughness || {}; + const metallicRoughness = materialDef.pbrMetallicRoughness || {}; materialParams.color = new Color( 1.0, 1.0, 1.0 ); materialParams.opacity = 1.0; if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { - var array = metallicRoughness.baseColorFactor; + const array = metallicRoughness.baseColorFactor; materialParams.color.fromArray( array ); materialParams.opacity = array[ 3 ]; @@ -2828,7 +2874,7 @@ var GLTFLoader = ( function () { } - var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; + const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; if ( alphaMode === ALPHA_MODES.BLEND ) { @@ -2890,7 +2936,7 @@ var GLTFLoader = ( function () { return Promise.all( pending ).then( function () { - var material; + let material; if ( materialType === GLTFMeshStandardSGMaterial ) { @@ -2918,16 +2964,16 @@ var GLTFLoader = ( function () { } ); - }; + } /** When Object3D instances are targeted by animation, they need unique names. */ - GLTFParser.prototype.createUniqueName = function ( originalName ) { + createUniqueName( originalName ) { - var sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); + const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); - var name = sanitizedName; + let name = sanitizedName; - for ( var i = 1; this.nodeNamesUsed[ name ]; ++ i ) { + for ( let i = 1; this.nodeNamesUsed[ name ]; ++ i ) { name = sanitizedName + '_' + i; @@ -2937,275 +2983,6 @@ var GLTFLoader = ( function () { return name; - }; - - /** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - */ - function computeBounds( geometry, primitiveDef, parser ) { - - var attributes = primitiveDef.attributes; - - var box = new 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 Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), - new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) - ); - - if ( accessor.normalized ) { - - var boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - box.min.multiplyScalar( boxScale ); - box.max.multiplyScalar( boxScale ); - - } - - } else { - - console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); - - return; - - } - - } else { - - return; - - } - - var targets = primitiveDef.targets; - - if ( targets !== undefined ) { - - var maxDisplacement = new Vector3(); - var vector = new 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 ] ) ) ); - - - if ( accessor.normalized ) { - - var boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); - vector.multiplyScalar( boxScale ); - - } - - // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative - // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets - // are used to implement key-frame animations and as such only two are active at a time - this results in very large - // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. - maxDisplacement.max( vector ); - - } else { - - console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); - - } - - } - - } - - // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. - box.expandByVector( maxDisplacement ); - - } - - geometry.boundingBox = box; - - var sphere = new Sphere(); - - box.getCenter( sphere.center ); - sphere.radius = box.min.distanceTo( box.max ) / 2; - - geometry.boundingSphere = sphere; - - } - - /** - * @param {BufferGeometry} geometry - * @param {GLTF.Primitive} primitiveDef - * @param {GLTFParser} parser - * @return {Promise} - */ - 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 {BufferGeometry} geometry - * @param {Number} drawMode - * @return {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 === 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; - } /** @@ -3216,11 +2993,11 @@ var GLTFLoader = ( function () { * @param {Array} primitives * @return {Promise>} */ - GLTFParser.prototype.loadGeometries = function ( primitives ) { + loadGeometries( primitives ) { - var parser = this; - var extensions = this.extensions; - var cache = this.primitiveCache; + const parser = this; + const extensions = this.extensions; + const cache = this.primitiveCache; function createDracoPrimitive( primitive ) { @@ -3234,15 +3011,15 @@ var GLTFLoader = ( function () { } - var pending = []; + const pending = []; - for ( var i = 0, il = primitives.length; i < il; i ++ ) { + for ( let i = 0, il = primitives.length; i < il; i ++ ) { - var primitive = primitives[ i ]; - var cacheKey = createPrimitiveKey( primitive ); + const primitive = primitives[ i ]; + const cacheKey = createPrimitiveKey( primitive ); // See if we've already created this geometry - var cached = cache[ cacheKey ]; + const cached = cache[ cacheKey ]; if ( cached ) { @@ -3251,7 +3028,7 @@ var GLTFLoader = ( function () { } else { - var geometryPromise; + let geometryPromise; if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { @@ -3276,27 +3053,27 @@ var GLTFLoader = ( function () { 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 ) { + loadMesh( meshIndex ) { - var parser = this; - var json = this.json; - var extensions = this.extensions; + const parser = this; + const json = this.json; + const extensions = this.extensions; - var meshDef = json.meshes[ meshIndex ]; - var primitives = meshDef.primitives; + const meshDef = json.meshes[ meshIndex ]; + const primitives = meshDef.primitives; - var pending = []; + const pending = []; - for ( var i = 0, il = primitives.length; i < il; i ++ ) { + for ( let i = 0, il = primitives.length; i < il; i ++ ) { - var material = primitives[ i ].material === undefined + const material = primitives[ i ].material === undefined ? createDefaultMaterial( this.cache ) : this.getDependency( 'material', primitives[ i ].material ); @@ -3308,26 +3085,26 @@ var GLTFLoader = ( function () { return Promise.all( pending ).then( function ( results ) { - var materials = results.slice( 0, results.length - 1 ); - var geometries = results[ results.length - 1 ]; + const materials = results.slice( 0, results.length - 1 ); + const geometries = results[ results.length - 1 ]; - var meshes = []; + const meshes = []; - for ( var i = 0, il = geometries.length; i < il; i ++ ) { + for ( let i = 0, il = geometries.length; i < il; i ++ ) { - var geometry = geometries[ i ]; - var primitive = primitives[ i ]; + const geometry = geometries[ i ]; + const primitive = primitives[ i ]; // 1. create Mesh - var mesh; + let mesh; - var material = materials[ i ]; + const material = materials[ i ]; if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || - primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || - primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || - primitive.mode === undefined ) { + 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 @@ -3398,9 +3175,9 @@ var GLTFLoader = ( function () { } - var group = new Group(); + const group = new Group(); - for ( var i = 0, il = meshes.length; i < il; i ++ ) { + for ( let i = 0, il = meshes.length; i < il; i ++ ) { group.add( meshes[ i ] ); @@ -3410,18 +3187,18 @@ var GLTFLoader = ( function () { } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras * @param {number} cameraIndex * @return {Promise} */ - GLTFParser.prototype.loadCamera = function ( cameraIndex ) { + loadCamera( cameraIndex ) { - var camera; - var cameraDef = this.json.cameras[ cameraIndex ]; - var params = cameraDef[ cameraDef.type ]; + let camera; + const cameraDef = this.json.cameras[ cameraIndex ]; + const params = cameraDef[ cameraDef.type ]; if ( ! params ) { @@ -3446,18 +3223,18 @@ var GLTFLoader = ( function () { 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 ) { + loadSkin( skinIndex ) { - var skinDef = this.json.skins[ skinIndex ]; + const skinDef = this.json.skins[ skinIndex ]; - var skinEntry = { joints: skinDef.joints }; + const skinEntry = { joints: skinDef.joints }; if ( skinDef.inverseBindMatrices === undefined ) { @@ -3473,33 +3250,33 @@ var GLTFLoader = ( function () { } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations * @param {number} animationIndex * @return {Promise} */ - GLTFParser.prototype.loadAnimation = function ( animationIndex ) { + loadAnimation( animationIndex ) { - var json = this.json; + const json = this.json; - var animationDef = json.animations[ animationIndex ]; + const animationDef = json.animations[ animationIndex ]; - var pendingNodes = []; - var pendingInputAccessors = []; - var pendingOutputAccessors = []; - var pendingSamplers = []; - var pendingTargets = []; + const pendingNodes = []; + const pendingInputAccessors = []; + const pendingOutputAccessors = []; + const pendingSamplers = []; + const pendingTargets = []; - for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) { + for ( let 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; + const channel = animationDef.channels[ i ]; + const sampler = animationDef.samplers[ channel.sampler ]; + const target = channel.target; + const name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. + const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; + const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; pendingNodes.push( this.getDependency( 'node', name ) ); pendingInputAccessors.push( this.getDependency( 'accessor', input ) ); @@ -3519,28 +3296,28 @@ var GLTFLoader = ( function () { ] ).then( function ( dependencies ) { - var nodes = dependencies[ 0 ]; - var inputAccessors = dependencies[ 1 ]; - var outputAccessors = dependencies[ 2 ]; - var samplers = dependencies[ 3 ]; - var targets = dependencies[ 4 ]; + const nodes = dependencies[ 0 ]; + const inputAccessors = dependencies[ 1 ]; + const outputAccessors = dependencies[ 2 ]; + const samplers = dependencies[ 3 ]; + const targets = dependencies[ 4 ]; - var tracks = []; + const tracks = []; - for ( var i = 0, il = nodes.length; i < il; i ++ ) { + for ( let 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 ]; + const node = nodes[ i ]; + const inputAccessor = inputAccessors[ i ]; + const outputAccessor = outputAccessors[ i ]; + const sampler = samplers[ i ]; + const target = targets[ i ]; if ( node === undefined ) continue; node.updateMatrix(); node.matrixAutoUpdate = true; - var TypedKeyframeTrack; + let TypedKeyframeTrack; switch ( PATH_PROPERTIES[ target.path ] ) { @@ -3563,11 +3340,11 @@ var GLTFLoader = ( function () { } - var targetName = node.name ? node.name : node.uuid; + const targetName = node.name ? node.name : node.uuid; - var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; + const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; - var targetNames = []; + const targetNames = []; if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { @@ -3588,14 +3365,14 @@ var GLTFLoader = ( function () { } - var outputArray = outputAccessor.array; + let outputArray = outputAccessor.array; if ( outputAccessor.normalized ) { - var scale = getNormalizedComponentScale( outputArray.constructor ); - var scaled = new Float32Array( outputArray.length ); + const scale = getNormalizedComponentScale( outputArray.constructor ); + const scaled = new Float32Array( outputArray.length ); - for ( var j = 0, jl = outputArray.length; j < jl; j ++ ) { + for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { scaled[ j ] = outputArray[ j ] * scale; @@ -3605,9 +3382,9 @@ var GLTFLoader = ( function () { } - for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) { + for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { - var track = new TypedKeyframeTrack( + const track = new TypedKeyframeTrack( targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], inputAccessor.array, outputArray, @@ -3638,25 +3415,25 @@ var GLTFLoader = ( function () { } - var name = animationDef.name ? animationDef.name : 'animation_' + animationIndex; + const name = animationDef.name ? animationDef.name : 'animation_' + animationIndex; return new AnimationClip( name, undefined, tracks ); } ); - }; + } - GLTFParser.prototype.createNodeMesh = function ( nodeIndex ) { + createNodeMesh( nodeIndex ) { - var json = this.json; - var parser = this; - var nodeDef = json.nodes[ nodeIndex ]; + const json = this.json; + const parser = this; + const nodeDef = json.nodes[ nodeIndex ]; if ( nodeDef.mesh === undefined ) return null; return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { - var node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); + const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); // if weights are provided on the node, override weights on the mesh. if ( nodeDef.weights !== undefined ) { @@ -3665,7 +3442,7 @@ var GLTFLoader = ( function () { if ( ! o.isMesh ) return; - for ( var i = 0, il = nodeDef.weights.length; i < il; i ++ ) { + for ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; @@ -3679,29 +3456,29 @@ var GLTFLoader = ( function () { } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy * @param {number} nodeIndex * @return {Promise} */ - GLTFParser.prototype.loadNode = function ( nodeIndex ) { + loadNode( nodeIndex ) { - var json = this.json; - var extensions = this.extensions; - var parser = this; + const json = this.json; + const extensions = this.extensions; + const parser = this; - var nodeDef = json.nodes[ nodeIndex ]; + const 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 ) : ''; + const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; return ( function () { - var pending = []; + const pending = []; - var meshPromise = parser._invokeOne( function ( ext ) { + const meshPromise = parser._invokeOne( function ( ext ) { return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); @@ -3737,7 +3514,7 @@ var GLTFLoader = ( function () { }() ).then( function ( objects ) { - var node; + let node; // .isBone isn't in glTF spec. See ._markDefs if ( nodeDef.isBone === true ) { @@ -3760,7 +3537,7 @@ var GLTFLoader = ( function () { if ( node !== objects[ 0 ] ) { - for ( var i = 0, il = objects.length; i < il; i ++ ) { + for ( let i = 0, il = objects.length; i < il; i ++ ) { node.add( objects[ i ] ); @@ -3781,7 +3558,7 @@ var GLTFLoader = ( function () { if ( nodeDef.matrix !== undefined ) { - var matrix = new Matrix4(); + const matrix = new Matrix4(); matrix.fromArray( nodeDef.matrix ); node.applyMatrix4( matrix ); @@ -3813,151 +3590,412 @@ var GLTFLoader = ( function () { } ); - }; + } /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes * @param {number} sceneIndex * @return {Promise} */ - GLTFParser.prototype.loadScene = function () { + loadScene( sceneIndex ) { + + const json = this.json; + const extensions = this.extensions; + const sceneDef = this.json.scenes[ sceneIndex ]; + const parser = this; - // scene node hierachy builder + // Loader returns Group, not Scene. + // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 + const scene = new Group(); + if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); - function buildNodeHierachy( nodeId, parentObject, json, parser ) { + assignExtrasToUserData( scene, sceneDef ); - var nodeDef = json.nodes[ nodeId ]; + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); - return parser.getDependency( 'node', nodeId ).then( function ( node ) { + const nodeIds = sceneDef.nodes || []; - if ( nodeDef.skin === undefined ) return node; + const pending = []; - // build skeleton here as well + for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { - var skinEntry; + pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); - return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { + } + + return Promise.all( pending ).then( function () { - skinEntry = skin; + return scene; - var pendingJoints = []; + } ); - for ( var i = 0, il = skinEntry.joints.length; i < il; i ++ ) { + } - pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); +} - } +function buildNodeHierachy( nodeId, parentObject, json, parser ) { + + const nodeDef = json.nodes[ nodeId ]; + + return parser.getDependency( 'node', nodeId ).then( function ( node ) { + + if ( nodeDef.skin === undefined ) return node; + + // build skeleton here as well + + let skinEntry; - return Promise.all( pendingJoints ); + return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { - } ).then( function ( jointNodes ) { + skinEntry = skin; - node.traverse( function ( mesh ) { + const pendingJoints = []; - if ( ! mesh.isMesh ) return; + for ( let i = 0, il = skinEntry.joints.length; i < il; i ++ ) { - var bones = []; - var boneInverses = []; + pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); - for ( var j = 0, jl = jointNodes.length; j < jl; j ++ ) { + } + + return Promise.all( pendingJoints ); - var jointNode = jointNodes[ j ]; + } ).then( function ( jointNodes ) { - if ( jointNode ) { + node.traverse( function ( mesh ) { - bones.push( jointNode ); + if ( ! mesh.isMesh ) return; - var mat = new Matrix4(); + const bones = []; + const boneInverses = []; - if ( skinEntry.inverseBindMatrices !== undefined ) { + for ( let j = 0, jl = jointNodes.length; j < jl; j ++ ) { - mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); + const jointNode = jointNodes[ j ]; - } + if ( jointNode ) { - boneInverses.push( mat ); + bones.push( jointNode ); - } else { + const mat = new Matrix4(); - console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); + if ( skinEntry.inverseBindMatrices !== undefined ) { - } + mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); } - mesh.bind( new Skeleton( bones, boneInverses ), mesh.matrixWorld ); + boneInverses.push( mat ); - } ); + } else { - return node; + console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); - } ); + } + + } + + mesh.bind( new Skeleton( bones, boneInverses ), mesh.matrixWorld ); + + } ); + + return node; + + } ); + + } ).then( function ( node ) { + + // build node hierachy + + parentObject.add( node ); + + const pending = []; + + if ( nodeDef.children ) { + + const children = nodeDef.children; + + for ( let i = 0, il = children.length; i < il; i ++ ) { + + const child = children[ i ]; + pending.push( buildNodeHierachy( child, node, json, parser ) ); + + } + + } + + return Promise.all( pending ); + + } ); + +} + +/** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + */ +function computeBounds( geometry, primitiveDef, parser ) { + + const attributes = primitiveDef.attributes; + + const box = new Box3(); + + if ( attributes.POSITION !== undefined ) { + + const accessor = parser.json.accessors[ attributes.POSITION ]; + + const min = accessor.min; + const max = accessor.max; + + // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. + + if ( min !== undefined && max !== undefined ) { + + box.set( + new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), + new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) + ); + + if ( accessor.normalized ) { + + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); + box.min.multiplyScalar( boxScale ); + box.max.multiplyScalar( boxScale ); + + } + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + + return; + + } + + } else { + + return; - } ).then( function ( node ) { + } + + const targets = primitiveDef.targets; + + if ( targets !== undefined ) { + + const maxDisplacement = new Vector3(); + const vector = new Vector3(); + + for ( let i = 0, il = targets.length; i < il; i ++ ) { + + const target = targets[ i ]; - // build node hierachy + if ( target.POSITION !== undefined ) { - parentObject.add( node ); + const accessor = parser.json.accessors[ target.POSITION ]; + const min = accessor.min; + const max = accessor.max; - var pending = []; + // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. - if ( nodeDef.children ) { + if ( min !== undefined && max !== undefined ) { - var children = nodeDef.children; + // 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 ] ) ) ); - for ( var i = 0, il = children.length; i < il; i ++ ) { - var child = children[ i ]; - pending.push( buildNodeHierachy( child, node, json, parser ) ); + if ( accessor.normalized ) { + + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); + vector.multiplyScalar( boxScale ); } + // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative + // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets + // are used to implement key-frame animations and as such only two are active at a time - this results in very large + // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. + maxDisplacement.max( vector ); + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + } - return Promise.all( pending ); + } + + } + + // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. + box.expandByVector( maxDisplacement ); + + } + + geometry.boundingBox = box; + + const sphere = new Sphere(); + + box.getCenter( sphere.center ); + sphere.radius = box.min.distanceTo( box.max ) / 2; + + geometry.boundingSphere = sphere; + +} + +/** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + * @return {Promise} + */ +function addPrimitiveAttributes( geometry, primitiveDef, parser ) { + + const attributes = primitiveDef.attributes; + + const pending = []; + + function assignAttributeAccessor( accessorIndex, attributeName ) { + + return parser.getDependency( 'accessor', accessorIndex ) + .then( function ( accessor ) { + + geometry.setAttribute( attributeName, accessor ); } ); + } + + for ( const gltfAttributeName in attributes ) { + + const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); + + // Skip attributes already provided by e.g. Draco extension. + if ( threeAttributeName in geometry.attributes ) continue; + + pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); + + } + + if ( primitiveDef.indices !== undefined && ! geometry.index ) { + + const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { + + geometry.setIndex( accessor ); + + } ); + + pending.push( accessor ); + + } + + assignExtrasToUserData( geometry, primitiveDef ); + + computeBounds( geometry, primitiveDef, parser ); + + return Promise.all( pending ).then( function () { + + return primitiveDef.targets !== undefined + ? addMorphTargets( geometry, primitiveDef.targets, parser ) + : geometry; + + } ); + +} + +/** + * @param {BufferGeometry} geometry + * @param {Number} drawMode + * @return {BufferGeometry} + */ +function toTrianglesDrawMode( geometry, drawMode ) { + + let index = geometry.getIndex(); + + // generate index if not present + + if ( index === null ) { + + const indices = []; + + const position = geometry.getAttribute( 'position' ); + + if ( position !== undefined ) { + + for ( let 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; + } - return function loadScene( sceneIndex ) { + } + + // - var json = this.json; - var extensions = this.extensions; - var sceneDef = this.json.scenes[ sceneIndex ]; - var parser = this; + const numberOfTriangles = index.count - 2; + const newIndices = []; - // Loader returns Group, not Scene. - // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 - var scene = new Group(); - if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); + if ( drawMode === TriangleFanDrawMode ) { - assignExtrasToUserData( scene, sceneDef ); + // gl.TRIANGLE_FAN - if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); + for ( let i = 1; i <= numberOfTriangles; i ++ ) { + + newIndices.push( index.getX( 0 ) ); + newIndices.push( index.getX( i ) ); + newIndices.push( index.getX( i + 1 ) ); + + } - var nodeIds = sceneDef.nodes || []; + } else { + + // gl.TRIANGLE_STRIP + + for ( let 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 ) ); - var pending = []; - for ( var i = 0, il = nodeIds.length; i < il; i ++ ) { + } else { - pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); + newIndices.push( index.getX( i + 2 ) ); + newIndices.push( index.getX( i + 1 ) ); + newIndices.push( index.getX( i ) ); } - return Promise.all( pending ).then( function () { + } - return scene; + } - } ); + if ( ( newIndices.length / 3 ) !== numberOfTriangles ) { - }; + console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' ); + + } + + // build final geometry - }(); + const newGeometry = geometry.clone(); + newGeometry.setIndex( newIndices ); - return GLTFLoader; + return newGeometry; -} )(); +} export { GLTFLoader }; diff --git a/examples/jsm/loaders/HDRCubeTextureLoader.js b/examples/jsm/loaders/HDRCubeTextureLoader.js index d8e02d35d47dee..47a882c354d63f 100644 --- a/examples/jsm/loaders/HDRCubeTextureLoader.js +++ b/examples/jsm/loaders/HDRCubeTextureLoader.js @@ -15,20 +15,18 @@ import { } from '../../../build/three.module.js'; import { RGBELoader } from '../loaders/RGBELoader.js'; -var HDRCubeTextureLoader = function ( manager ) { +class HDRCubeTextureLoader extends Loader { - Loader.call( this, manager ); + constructor( manager ) { - this.hdrLoader = new RGBELoader(); - this.type = UnsignedByteType; + super( manager ); -}; + this.hdrLoader = new RGBELoader(); + this.type = UnsignedByteType; -HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), { - - constructor: HDRCubeTextureLoader, + } - load: function ( urls, onLoad, onProgress, onError ) { + load( urls, onLoad, onProgress, onError ) { if ( ! Array.isArray( urls ) ) { @@ -43,7 +41,7 @@ HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype } - var texture = new CubeTexture(); + const texture = new CubeTexture(); texture.type = this.type; @@ -78,9 +76,9 @@ HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype } - var scope = this; + const scope = this; - var loaded = 0; + let loaded = 0; function loadHDRData( i, onLoad, onProgress, onError ) { @@ -92,13 +90,13 @@ HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype loaded ++; - var texData = scope.hdrLoader.parse( buffer ); + const texData = scope.hdrLoader.parse( buffer ); if ( ! texData ) return; if ( texData.data !== undefined ) { - var dataTexture = new DataTexture( texData.data, texData.width, texData.height ); + const dataTexture = new DataTexture( texData.data, texData.width, texData.height ); dataTexture.type = texture.type; dataTexture.encoding = texture.encoding; @@ -122,7 +120,7 @@ HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype } - for ( var i = 0; i < urls.length; i ++ ) { + for ( let i = 0; i < urls.length; i ++ ) { loadHDRData( i, onLoad, onProgress, onError ); @@ -130,9 +128,9 @@ HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype return texture; - }, + } - setDataType: function ( value ) { + setDataType( value ) { this.type = value; this.hdrLoader.setDataType( value ); @@ -141,6 +139,6 @@ HDRCubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype } -} ); +} export { HDRCubeTextureLoader }; diff --git a/examples/jsm/loaders/IFCLoader.js b/examples/jsm/loaders/IFCLoader.js index b940214dbdd2fe..2150a7972ae9ad 100644 --- a/examples/jsm/loaders/IFCLoader.js +++ b/examples/jsm/loaders/IFCLoader.js @@ -16,23 +16,21 @@ import { BufferAttribute, } from '../../../build/three.module.js'; -var ifcAPI = new IfcAPI(); +const ifcAPI = new IfcAPI(); -function IFCLoader( manager ) { +class IFCLoader extends Loader { - Loader.call( this, manager ); + constructor( manager ) { -} - -IFCLoader.prototype = Object.assign( Object.create( Loader.prototype ), { + super( manager ); - constructor: IFCLoader, + } - load: function ( url, onLoad, onProgress, onError ) { + load( url, onLoad, onProgress, onError ) { - var scope = this; + const scope = this; - var loader = new FileLoader( scope.manager ); + const loader = new FileLoader( scope.manager ); loader.setPath( scope.path ); loader.setResponseType( 'arraybuffer' ); loader.setRequestHeader( scope.requestHeader ); @@ -66,9 +64,9 @@ IFCLoader.prototype = Object.assign( Object.create( Loader.prototype ), { onError ); - }, + } - parse: async function ( buffer ) { + async parse( buffer ) { if ( ifcAPI.wasmModule === undefined ) { @@ -76,18 +74,18 @@ IFCLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var data = new Uint8Array( buffer ); - var modelID = ifcAPI.OpenModel( 'example.ifc', data ); + const data = new Uint8Array( buffer ); + const modelID = ifcAPI.OpenModel( 'example.ifc', data ); return loadAllGeometry( modelID ); function loadAllGeometry( modelID ) { - var flatMeshes = getFlatMeshes( modelID ); - var mainObject = new Object3D(); - for ( var i = 0; i < flatMeshes.size(); i ++ ) { + const flatMeshes = getFlatMeshes( modelID ); + const mainObject = new Object3D(); + for ( let i = 0; i < flatMeshes.size(); i ++ ) { - var placedGeometries = flatMeshes.get( i ).geometries; - for ( var j = 0; j < placedGeometries.size(); j ++ ) + const placedGeometries = flatMeshes.get( i ).geometries; + for ( let j = 0; j < placedGeometries.size(); j ++ ) mainObject.add( getPlacedGeometry( modelID, placedGeometries.get( j ) ) ); } @@ -98,16 +96,16 @@ IFCLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function getFlatMeshes( modelID ) { - var flatMeshes = ifcAPI.LoadAllGeometry( modelID ); + const flatMeshes = ifcAPI.LoadAllGeometry( modelID ); return flatMeshes; } function getPlacedGeometry( modelID, placedGeometry ) { - var geometry = getBufferGeometry( modelID, placedGeometry ); - var material = getMeshMaterial( placedGeometry.color ); - var mesh = new Mesh( geometry, material ); + const geometry = getBufferGeometry( modelID, placedGeometry ); + const material = getMeshMaterial( placedGeometry.color ); + const mesh = new Mesh( geometry, material ); mesh.matrix = getMeshMatrix( placedGeometry.flatTransformation ); mesh.matrixAutoUpdate = false; return mesh; @@ -116,27 +114,27 @@ IFCLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function getBufferGeometry( modelID, placedGeometry ) { - var geometry = ifcAPI.GetGeometry( + const geometry = ifcAPI.GetGeometry( modelID, placedGeometry.geometryExpressID ); - var verts = ifcAPI.GetVertexArray( + const verts = ifcAPI.GetVertexArray( geometry.GetVertexData(), geometry.GetVertexDataSize() ); - var indices = ifcAPI.GetIndexArray( + const indices = ifcAPI.GetIndexArray( geometry.GetIndexData(), geometry.GetIndexDataSize() ); - var bufferGeometry = ifcGeometryToBuffer( verts, indices ); + const bufferGeometry = ifcGeometryToBuffer( verts, indices ); return bufferGeometry; } function getMeshMaterial( color ) { - var col = new Color( color.x, color.y, color.z ); - var material = new MeshPhongMaterial( { color: col, side: DoubleSide } ); + const col = new Color( color.x, color.y, color.z ); + const material = new MeshPhongMaterial( { color: col, side: DoubleSide } ); material.transparent = color.w !== 1; if ( material.transparent ) material.opacity = color.w; return material; @@ -145,7 +143,7 @@ IFCLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function getMeshMatrix( matrix ) { - var mat = new Matrix4(); + const mat = new Matrix4(); mat.fromArray( matrix ); // mat.elements[15 - 3] *= 0.001; // mat.elements[15 - 2] *= 0.001; @@ -156,8 +154,8 @@ IFCLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function ifcGeometryToBuffer( vertexData, indexData ) { - var geometry = new BufferGeometry(); - var buffer32 = new InterleavedBuffer( vertexData, 6 ); + const geometry = new BufferGeometry(); + const buffer32 = new InterleavedBuffer( vertexData, 6 ); geometry.setAttribute( 'position', new InterleavedBufferAttribute( buffer32, 3, 0 ) @@ -172,6 +170,7 @@ IFCLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } } -} ); + +} export { IFCLoader }; diff --git a/examples/jsm/loaders/KTXLoader.js b/examples/jsm/loaders/KTXLoader.js index ea929a34c5b15e..f0bcd2773073ba 100644 --- a/examples/jsm/loaders/KTXLoader.js +++ b/examples/jsm/loaders/KTXLoader.js @@ -10,19 +10,17 @@ import { */ -var KTXLoader = function ( manager ) { +class KTXLoader extends CompressedTextureLoader { - CompressedTextureLoader.call( this, manager ); + constructor( manager ) { -}; + super( manager ); -KTXLoader.prototype = Object.assign( Object.create( CompressedTextureLoader.prototype ), { - - constructor: KTXLoader, + } - parse: function ( buffer, loadMipmaps ) { + parse( buffer, loadMipmaps ) { - var ktx = new KhronosTextureContainer( buffer, 1 ); + const ktx = new KhronosTextureContainer( buffer, 1 ); return { mipmaps: ktx.mipmaps( loadMipmaps ), @@ -35,9 +33,17 @@ KTXLoader.prototype = Object.assign( Object.create( CompressedTextureLoader.prot } -} ); +} + + +const HEADER_LEN = 12 + ( 13 * 4 ); // identifier + header elements (not including key value meta-data pairs) +// load types +const COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D() +//const COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D() +//const TEX_2D = 2; // uses a gl.texImage2D() +//const TEX_3D = 3; // uses a gl.texImage3D() -var KhronosTextureContainer = ( function () { +class KhronosTextureContainer { /** * @param {ArrayBuffer} arrayBuffer- contents of the KTX container file @@ -45,14 +51,14 @@ var KhronosTextureContainer = ( function () { * @param {boolean} threeDExpected- provision for indicating that data should be a 3D texture, not implemented * @param {boolean} textureArrayExpected- provision for indicating that data should be a texture array, not implemented */ - function KhronosTextureContainer( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) { + constructor( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) { this.arrayBuffer = arrayBuffer; // Test that it is a ktx formatted file, based on the first 12 bytes, character representation is: // '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n' // 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A - var identifier = new Uint8Array( this.arrayBuffer, 0, 12 ); + const identifier = new Uint8Array( this.arrayBuffer, 0, 12 ); if ( identifier[ 0 ] !== 0xAB || identifier[ 1 ] !== 0x4B || identifier[ 2 ] !== 0x54 || @@ -72,10 +78,10 @@ var KhronosTextureContainer = ( function () { } // load the reset of the header in native 32 bit uint - var dataSize = Uint32Array.BYTES_PER_ELEMENT; - var headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize ); - var endianness = headerDataView.getUint32( 0, true ); - var littleEndian = endianness === 0x04030201; + const dataSize = Uint32Array.BYTES_PER_ELEMENT; + const headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize ); + const endianness = headerDataView.getUint32( 0, true ); + const littleEndian = endianness === 0x04030201; this.glType = headerDataView.getUint32( 1 * dataSize, littleEndian ); // must be 0 for compressed textures this.glTypeSize = headerDataView.getUint32( 2 * dataSize, littleEndian ); // must be 1 for compressed textures @@ -126,29 +132,28 @@ var KhronosTextureContainer = ( function () { // we now have a completely validated file, so could use existence of loadType as success // would need to make this more elaborate & adjust checks above to support more than one load type - this.loadType = KhronosTextureContainer.COMPRESSED_2D; + this.loadType = COMPRESSED_2D; } - // return mipmaps for js - KhronosTextureContainer.prototype.mipmaps = function ( loadMipmaps ) { + mipmaps( loadMipmaps ) { - var mipmaps = []; + const mipmaps = []; // initialize width & height for level 1 - var dataOffset = KhronosTextureContainer.HEADER_LEN + this.bytesOfKeyValueData; - var width = this.pixelWidth; - var height = this.pixelHeight; - var mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1; + let dataOffset = HEADER_LEN + this.bytesOfKeyValueData; + let width = this.pixelWidth; + let height = this.pixelHeight; + const mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1; - for ( var level = 0; level < mipmapCount; level ++ ) { + for ( let level = 0; level < mipmapCount; level ++ ) { - var imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps + const imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps dataOffset += 4; // size of the image + 4 for the imageSize field - for ( var face = 0; face < this.numberOfFaces; face ++ ) { + for ( let face = 0; face < this.numberOfFaces; face ++ ) { - var byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize ); + const byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize ); mipmaps.push( { 'data': byteArray, 'width': width, 'height': height } ); @@ -164,17 +169,8 @@ var KhronosTextureContainer = ( function () { return mipmaps; - }; - - KhronosTextureContainer.HEADER_LEN = 12 + ( 13 * 4 ); // identifier + header elements (not including key value meta-data pairs) - // load types - KhronosTextureContainer.COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D() - KhronosTextureContainer.COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D() - KhronosTextureContainer.TEX_2D = 2; // uses a gl.texImage2D() - KhronosTextureContainer.TEX_3D = 3; // uses a gl.texImage3D() - - return KhronosTextureContainer; + } -}() ); +} export { KTXLoader }; diff --git a/examples/jsm/loaders/LWOLoader.js b/examples/jsm/loaders/LWOLoader.js index 3c8e38ddf2718b..93434d50641e7f 100644 --- a/examples/jsm/loaders/LWOLoader.js +++ b/examples/jsm/loaders/LWOLoader.js @@ -41,32 +41,28 @@ import { import { IFFParser } from './lwo/IFFParser.js'; -var lwoTree; +let _lwoTree; -var LWOLoader = function ( manager, parameters ) { +class LWOLoader extends Loader { - Loader.call( this, manager ); + constructor( manager, parameters = {} ) { - parameters = parameters || {}; + super( manager ); - this.resourcePath = ( parameters.resourcePath !== undefined ) ? parameters.resourcePath : ''; + this.resourcePath = ( parameters.resourcePath !== undefined ) ? parameters.resourcePath : ''; -}; - -LWOLoader.prototype = Object.assign( Object.create( Loader.prototype ), { - - constructor: LWOLoader, + } - load: function ( url, onLoad, onProgress, onError ) { + load( url, onLoad, onProgress, onError ) { - var scope = this; + const scope = this; - var path = ( scope.path === '' ) ? extractParentUrl( url, 'Objects' ) : scope.path; + const path = ( scope.path === '' ) ? extractParentUrl( url, 'Objects' ) : scope.path; // give the mesh a default name based on the filename - var modelName = url.split( path ).pop().split( '.' )[ 0 ]; + const modelName = url.split( path ).pop().split( '.' )[ 0 ]; - var loader = new FileLoader( this.manager ); + const loader = new FileLoader( this.manager ); loader.setPath( scope.path ); loader.setResponseType( 'arraybuffer' ); @@ -98,34 +94,32 @@ LWOLoader.prototype = Object.assign( Object.create( Loader.prototype ), { }, onProgress, onError ); - }, + } - parse: function ( iffBuffer, path, modelName ) { + parse( iffBuffer, path, modelName ) { - lwoTree = new IFFParser().parse( iffBuffer ); + _lwoTree = new IFFParser().parse( iffBuffer ); // console.log( 'lwoTree', lwoTree ); - var textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); + const textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); return new LWOTreeParser( textureLoader ).parse( modelName ); } -} ); +} // Parse the lwoTree object -function LWOTreeParser( textureLoader ) { - - this.textureLoader = textureLoader; +class LWOTreeParser { -} + constructor( textureLoader ) { -LWOTreeParser.prototype = { + this.textureLoader = textureLoader; - constructor: LWOTreeParser, + } - parse: function ( modelName ) { + parse( modelName ) { this.materials = new MaterialParser( this.textureLoader ).parse(); this.defaultLayerName = modelName; @@ -137,24 +131,24 @@ LWOTreeParser.prototype = { meshes: this.meshes, }; - }, + } parseLayers() { // array of all meshes for building hierarchy - var meshes = []; + const meshes = []; // final array containing meshes with scene graph hierarchy set up - var finalMeshes = []; + const finalMeshes = []; - var geometryParser = new GeometryParser(); + const geometryParser = new GeometryParser(); - var scope = this; - lwoTree.layers.forEach( function ( layer ) { + const scope = this; + _lwoTree.layers.forEach( function ( layer ) { - var geometry = geometryParser.parse( layer.geometry, layer ); + const geometry = geometryParser.parse( layer.geometry, layer ); - var mesh = scope.parseMesh( geometry, layer ); + const mesh = scope.parseMesh( geometry, layer ); meshes[ layer.number ] = mesh; @@ -168,13 +162,13 @@ LWOTreeParser.prototype = { return finalMeshes; - }, + } parseMesh( geometry, layer ) { - var mesh; + let mesh; - var materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type ); + const materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type ); this.duplicateUVs( geometry, materials ); @@ -189,7 +183,7 @@ LWOTreeParser.prototype = { return mesh; - }, + } // TODO: may need to be reversed in z to convert LWO to three.js coordinates applyPivots( meshes ) { @@ -198,7 +192,7 @@ LWOTreeParser.prototype = { mesh.traverse( function ( child ) { - var pivot = child.userData.pivot; + const pivot = child.userData.pivot; child.position.x += pivot[ 0 ]; child.position.y += pivot[ 1 ]; @@ -206,7 +200,7 @@ LWOTreeParser.prototype = { if ( child.parent ) { - var parentPivot = child.parent.userData.pivot; + const parentPivot = child.parent.userData.pivot; child.position.x -= parentPivot[ 0 ]; child.position.y -= parentPivot[ 1 ]; @@ -218,13 +212,13 @@ LWOTreeParser.prototype = { } ); - }, + } getMaterials( namesArray, type ) { - var materials = []; + const materials = []; - var scope = this; + const scope = this; namesArray.forEach( function ( name, i ) { @@ -237,7 +231,7 @@ LWOTreeParser.prototype = { materials.forEach( function ( mat, i ) { - var spec = { + const spec = { color: mat.color, }; @@ -259,12 +253,12 @@ LWOTreeParser.prototype = { } // if there is only one material, return that directly instead of array - var filtered = materials.filter( Boolean ); + const filtered = materials.filter( Boolean ); if ( filtered.length === 1 ) return filtered[ 0 ]; return materials; - }, + } getMaterialByName( name ) { @@ -274,12 +268,12 @@ LWOTreeParser.prototype = { } )[ 0 ]; - }, + } // If the material has an aoMap, duplicate UVs duplicateUVs( geometry, materials ) { - var duplicateUVs = false; + let duplicateUVs = false; if ( ! Array.isArray( materials ) ) { @@ -299,34 +293,32 @@ LWOTreeParser.prototype = { geometry.setAttribute( 'uv2', new BufferAttribute( geometry.attributes.uv.array, 2 ) ); - }, - -}; + } -function MaterialParser( textureLoader ) { +} - this.textureLoader = textureLoader; +class MaterialParser { -} + constructor( textureLoader ) { -MaterialParser.prototype = { + this.textureLoader = textureLoader; - constructor: MaterialParser, + } - parse: function () { + parse() { - var materials = []; + const materials = []; this.textures = {}; - for ( var name in lwoTree.materials ) { + for ( const name in _lwoTree.materials ) { - if ( lwoTree.format === 'LWO3' ) { + if ( _lwoTree.format === 'LWO3' ) { - materials.push( this.parseMaterial( lwoTree.materials[ name ], name, lwoTree.textures ) ); + materials.push( this.parseMaterial( _lwoTree.materials[ name ], name, _lwoTree.textures ) ); - } else if ( lwoTree.format === 'LWO2' ) { + } else if ( _lwoTree.format === 'LWO2' ) { - materials.push( this.parseMaterialLwo2( lwoTree.materials[ name ], name, lwoTree.textures ) ); + materials.push( this.parseMaterialLwo2( _lwoTree.materials[ name ], name, _lwoTree.textures ) ); } @@ -334,48 +326,48 @@ MaterialParser.prototype = { return materials; - }, + } parseMaterial( materialData, name, textures ) { - var params = { + let params = { name: name, side: this.getSide( materialData.attributes ), flatShading: this.getSmooth( materialData.attributes ), }; - var connections = this.parseConnections( materialData.connections, materialData.nodes ); + const connections = this.parseConnections( materialData.connections, materialData.nodes ); - var maps = this.parseTextureNodes( connections.maps ); + const maps = this.parseTextureNodes( connections.maps ); this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps ); - var attributes = this.parseAttributes( connections.attributes, maps ); + const attributes = this.parseAttributes( connections.attributes, maps ); this.parseEnvMap( connections, maps, attributes ); params = Object.assign( maps, params ); params = Object.assign( params, attributes ); - var materialType = this.getMaterialType( connections.attributes ); + const materialType = this.getMaterialType( connections.attributes ); return new materialType( params ); - }, + } parseMaterialLwo2( materialData, name/*, textures*/ ) { - var params = { + let params = { name: name, side: this.getSide( materialData.attributes ), flatShading: this.getSmooth( materialData.attributes ), }; - var attributes = this.parseAttributes( materialData.attributes, {} ); + const attributes = this.parseAttributes( materialData.attributes, {} ); params = Object.assign( params, attributes ); return new MeshPhongMaterial( params ); - }, + } // Note: converting from left to right handed coords by switching x -> -x in vertices, and // then switching mat FrontSide -> BackSide @@ -394,31 +386,31 @@ MaterialParser.prototype = { } - }, + } getSmooth( attributes ) { if ( ! attributes.smooth ) return true; return ! attributes.smooth; - }, + } parseConnections( connections, nodes ) { - var materialConnections = { + const materialConnections = { maps: {} }; - var inputName = connections.inputName; - var inputNodeName = connections.inputNodeName; - var nodeName = connections.nodeName; + const inputName = connections.inputName; + const inputNodeName = connections.inputNodeName; + const nodeName = connections.nodeName; - var scope = this; + const scope = this; inputName.forEach( function ( name, index ) { if ( name === 'Material' ) { - var matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes ); + const matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes ); materialConnections.attributes = matNode.attributes; materialConnections.envMap = matNode.fileName; materialConnections.name = inputNodeName[ index ]; @@ -439,30 +431,30 @@ MaterialParser.prototype = { return materialConnections; - }, + } getNodeByRefName( refName, nodes ) { - for ( var name in nodes ) { + for ( const name in nodes ) { if ( nodes[ name ].refName === refName ) return nodes[ name ]; } - }, + } parseTextureNodes( textureNodes ) { - var maps = {}; + const maps = {}; - for ( var name in textureNodes ) { + for ( const name in textureNodes ) { - var node = textureNodes[ name ]; - var path = node.fileName; + const node = textureNodes[ name ]; + const path = node.fileName; if ( ! path ) return; - var texture = this.loadTexture( path ); + const texture = this.loadTexture( path ); if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode ); if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode ); @@ -513,24 +505,24 @@ MaterialParser.prototype = { return maps; - }, + } // maps can also be defined on individual material attributes, parse those here // This occurs on Standard (Phong) surfaces parseAttributeImageMaps( attributes, textures, maps ) { - for ( var name in attributes ) { + for ( const name in attributes ) { - var attribute = attributes[ name ]; + const attribute = attributes[ name ]; if ( attribute.maps ) { - var mapData = attribute.maps[ 0 ]; + const mapData = attribute.maps[ 0 ]; - var path = this.getTexturePathByIndex( mapData.imageIndex, textures ); + const path = this.getTexturePathByIndex( mapData.imageIndex, textures ); if ( ! path ) return; - var texture = this.loadTexture( path ); + const texture = this.loadTexture( path ); if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w ); if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h ); @@ -577,11 +569,11 @@ MaterialParser.prototype = { } - }, + } parseAttributes( attributes, maps ) { - var params = {}; + const params = {}; // don't use color data if color map is present if ( attributes.Color && ! maps.map ) { @@ -608,7 +600,7 @@ MaterialParser.prototype = { return params; - }, + } parsePhysicalAttributes( params, attributes/*, maps*/ ) { @@ -624,7 +616,7 @@ MaterialParser.prototype = { } - }, + } parseStandardAttributes( params, attributes, maps ) { @@ -648,7 +640,7 @@ MaterialParser.prototype = { if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value; if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value; - }, + } parsePhongAttributes( params, attributes, maps ) { @@ -694,13 +686,13 @@ MaterialParser.prototype = { if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 ); - }, + } parseEnvMap( connections, maps, attributes ) { if ( connections.envMap ) { - var envMap = this.loadTexture( connections.envMap ); + const envMap = this.loadTexture( connections.envMap ); if ( attributes.transparent && attributes.opacity < 0.999 ) { @@ -726,16 +718,16 @@ MaterialParser.prototype = { } - }, + } // get texture defined at top level by its index getTexturePathByIndex( index ) { - var fileName = ''; + let fileName = ''; - if ( ! lwoTree.textures ) return fileName; + if ( ! _lwoTree.textures ) return fileName; - lwoTree.textures.forEach( function ( texture ) { + _lwoTree.textures.forEach( function ( texture ) { if ( texture.index === index ) fileName = texture.fileName; @@ -743,15 +735,13 @@ MaterialParser.prototype = { return fileName; - }, + } loadTexture( path ) { if ( ! path ) return null; - var texture; - - texture = this.textureLoader.load( + const texture = this.textureLoader.load( path, undefined, undefined, @@ -764,7 +754,7 @@ MaterialParser.prototype = { return texture; - }, + } // 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge getWrappingType( num ) { @@ -780,7 +770,7 @@ MaterialParser.prototype = { } - }, + } getMaterialType( nodeData ) { @@ -790,21 +780,17 @@ MaterialParser.prototype = { } -}; - -function GeometryParser() {} - -GeometryParser.prototype = { +} - constructor: GeometryParser, +class GeometryParser { parse( geoData, layer ) { - var geometry = new BufferGeometry(); + const geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( geoData.points, 3 ) ); - var indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions ); + const indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions ); geometry.setIndex( indices ); this.parseGroups( geometry, geoData ); @@ -817,25 +803,25 @@ GeometryParser.prototype = { // TODO: z may need to be reversed to account for coordinate system change geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); - // var userData = geometry.userData; + // let userData = geometry.userData; // geometry = geometry.toNonIndexed() // geometry.userData = userData; return geometry; - }, + } // split quads into tris splitIndices( indices, polygonDimensions ) { - var remappedIndices = []; + const remappedIndices = []; - var i = 0; + let i = 0; polygonDimensions.forEach( function ( dim ) { if ( dim < 4 ) { - for ( var k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] ); + for ( let k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] ); } else if ( dim === 4 ) { @@ -852,7 +838,7 @@ GeometryParser.prototype = { } else if ( dim > 4 ) { - for ( var k = 1; k < dim - 1; k ++ ) { + for ( let k = 1; k < dim - 1; k ++ ) { remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] ); @@ -868,31 +854,32 @@ GeometryParser.prototype = { return remappedIndices; - }, + } // NOTE: currently ignoring poly indices and assuming that they are intelligently ordered parseGroups( geometry, geoData ) { - var tags = lwoTree.tags; - var matNames = []; + const tags = _lwoTree.tags; + const matNames = []; - var elemSize = 3; + let elemSize = 3; if ( geoData.type === 'lines' ) elemSize = 2; if ( geoData.type === 'points' ) elemSize = 1; - var remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices ); + const remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices ); - var indexNum = 0; // create new indices in numerical order - var indexPairs = {}; // original indices mapped to numerical indices + let indexNum = 0; // create new indices in numerical order + const indexPairs = {}; // original indices mapped to numerical indices - var prevMaterialIndex; + let prevMaterialIndex; + let materialIndex; - var prevStart = 0; - var currentCount = 0; + let prevStart = 0; + let currentCount = 0; - for ( var i = 0; i < remappedIndices.length; i += 2 ) { + for ( let i = 0; i < remappedIndices.length; i += 2 ) { - var materialIndex = remappedIndices[ i + 1 ]; + materialIndex = remappedIndices[ i + 1 ]; if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ]; @@ -900,7 +887,7 @@ GeometryParser.prototype = { if ( materialIndex !== prevMaterialIndex ) { - var currentIndex; + let currentIndex; if ( indexPairs[ tags[ prevMaterialIndex ] ] ) { currentIndex = indexPairs[ tags[ prevMaterialIndex ] ]; @@ -930,7 +917,7 @@ GeometryParser.prototype = { // the loop above doesn't add the last group, do that here. if ( geometry.groups.length > 0 ) { - var currentIndex; + let currentIndex; if ( indexPairs[ tags[ materialIndex ] ] ) { currentIndex = indexPairs[ tags[ materialIndex ] ]; @@ -950,11 +937,11 @@ GeometryParser.prototype = { // Mat names from TAGS chunk, used to build up an array of materials for this geometry geometry.userData.matNames = matNames; - }, + } splitMaterialIndices( polygonDimensions, indices ) { - var remappedIndices = []; + const remappedIndices = []; polygonDimensions.forEach( function ( dim, i ) { @@ -969,7 +956,7 @@ GeometryParser.prototype = { } else { // ignore > 4 for now - for ( var k = 0; k < dim - 2; k ++ ) { + for ( let k = 0; k < dim - 2; k ++ ) { remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] ); @@ -981,7 +968,7 @@ GeometryParser.prototype = { return remappedIndices; - }, + } // UV maps: // 1: are defined via index into an array of points, not into a geometry @@ -995,16 +982,16 @@ GeometryParser.prototype = { parseUVs( geometry, layer ) { // start by creating a UV map set to zero for the whole geometry - var remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () { + const remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () { return 0; } ); - for ( var name in layer.uvs ) { + for ( const name in layer.uvs ) { - var uvs = layer.uvs[ name ].uvs; - var uvIndices = layer.uvs[ name ].uvIndices; + const uvs = layer.uvs[ name ].uvs; + const uvIndices = layer.uvs[ name ].uvIndices; uvIndices.forEach( function ( i, j ) { @@ -1017,20 +1004,20 @@ GeometryParser.prototype = { geometry.setAttribute( 'uv', new Float32BufferAttribute( remappedUVs, 2 ) ); - }, + } parseMorphTargets( geometry, layer ) { - var num = 0; - for ( var name in layer.morphTargets ) { + let num = 0; + for ( const name in layer.morphTargets ) { - var remappedPoints = geometry.attributes.position.array.slice(); + const remappedPoints = geometry.attributes.position.array.slice(); if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = []; - var morphPoints = layer.morphTargets[ name ].points; - var morphIndices = layer.morphTargets[ name ].indices; - var type = layer.morphTargets[ name ].type; + const morphPoints = layer.morphTargets[ name ].points; + const morphIndices = layer.morphTargets[ name ].indices; + const type = layer.morphTargets[ name ].type; morphIndices.forEach( function ( i, j ) { @@ -1059,16 +1046,16 @@ GeometryParser.prototype = { geometry.morphTargetsRelative = false; - }, + } -}; +} // ************** UTILITY FUNCTIONS ************** function extractParentUrl( url, dir ) { - var index = url.indexOf( dir ); + const index = url.indexOf( dir ); if ( index === - 1 ) return './'; diff --git a/examples/jsm/loaders/MTLLoader.js b/examples/jsm/loaders/MTLLoader.js index 51c8482e3162ae..fba96470d7dd5c 100644 --- a/examples/jsm/loaders/MTLLoader.js +++ b/examples/jsm/loaders/MTLLoader.js @@ -15,15 +15,13 @@ import { * Loads a Wavefront .mtl file specifying materials */ -var MTLLoader = function ( manager ) { +class MTLLoader extends Loader { - Loader.call( this, manager ); + constructor( manager ) { -}; + super( manager ); -MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), { - - constructor: MTLLoader, + } /** * Loads and parses a MTL asset from a URL. @@ -38,13 +36,13 @@ MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), { * @note In order for relative texture references to resolve correctly * you must call setResourcePath() explicitly prior to load. */ - load: function ( url, onLoad, onProgress, onError ) { + load( url, onLoad, onProgress, onError ) { - var scope = this; + const scope = this; - var path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; + const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; - var loader = new FileLoader( this.manager ); + const loader = new FileLoader( this.manager ); loader.setPath( this.path ); loader.setRequestHeader( this.requestHeader ); loader.setWithCredentials( this.withCredentials ); @@ -72,36 +70,36 @@ MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), { }, onProgress, onError ); - }, + } - setMaterialOptions: function ( value ) { + setMaterialOptions( value ) { this.materialOptions = value; return this; - }, + } /** * Parses a MTL file. * * @param {String} text - Content of MTL file - * @return {MTLLoader.MaterialCreator} + * @return {MaterialCreator} * * @see setPath setResourcePath * * @note In order for relative texture references to resolve correctly * you must call setResourcePath() explicitly prior to parse. */ - parse: function ( text, path ) { + parse( text, path ) { - var lines = text.split( '\n' ); - var info = {}; - var delimiter_pattern = /\s+/; - var materialsInfo = {}; + const lines = text.split( '\n' ); + let info = {}; + const delimiter_pattern = /\s+/; + const materialsInfo = {}; - for ( var i = 0; i < lines.length; i ++ ) { + for ( let i = 0; i < lines.length; i ++ ) { - var line = lines[ i ]; + let line = lines[ i ]; line = line.trim(); if ( line.length === 0 || line.charAt( 0 ) === '#' ) { @@ -111,12 +109,12 @@ MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var pos = line.indexOf( ' ' ); + const pos = line.indexOf( ' ' ); - var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; + let key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; key = key.toLowerCase(); - var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; + let value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; value = value.trim(); if ( key === 'newmtl' ) { @@ -130,7 +128,7 @@ MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), { if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) { - var ss = value.split( delimiter_pattern, 3 ); + const ss = value.split( delimiter_pattern, 3 ); info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; } else { @@ -143,7 +141,7 @@ MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var materialCreator = new MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions ); + const materialCreator = new MaterialCreator( this.resourcePath || path, this.materialOptions ); materialCreator.setCrossOrigin( this.crossOrigin ); materialCreator.setManager( this.manager ); materialCreator.setMaterials( materialsInfo ); @@ -151,7 +149,7 @@ MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } -} ); +} /** * Create a new MTLLoader.MaterialCreator @@ -168,69 +166,67 @@ MTLLoader.prototype = Object.assign( Object.create( Loader.prototype ), { * @constructor */ -MTLLoader.MaterialCreator = function ( baseUrl, options ) { - - this.baseUrl = baseUrl || ''; - this.options = options; - this.materialsInfo = {}; - this.materials = {}; - this.materialsArray = []; - this.nameLookup = {}; +class MaterialCreator { - this.side = ( this.options && this.options.side ) ? this.options.side : FrontSide; - this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : RepeatWrapping; + constructor( baseUrl = '', options = {} ) { -}; + this.baseUrl = baseUrl; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; -MTLLoader.MaterialCreator.prototype = { + this.crossOrigin = 'anonymous'; - constructor: MTLLoader.MaterialCreator, + this.side = ( this.options.side !== undefined ) ? this.options.side : FrontSide; + this.wrap = ( this.options.wrap !== undefined ) ? this.options.wrap : RepeatWrapping; - crossOrigin: 'anonymous', + } - setCrossOrigin: function ( value ) { + setCrossOrigin( value ) { this.crossOrigin = value; return this; - }, + } - setManager: function ( value ) { + setManager( value ) { this.manager = value; - }, + } - setMaterials: function ( materialsInfo ) { + setMaterials( materialsInfo ) { this.materialsInfo = this.convert( materialsInfo ); this.materials = {}; this.materialsArray = []; this.nameLookup = {}; - }, + } - convert: function ( materialsInfo ) { + convert( materialsInfo ) { if ( ! this.options ) return materialsInfo; - var converted = {}; + const converted = {}; - for ( var mn in materialsInfo ) { + for ( const mn in materialsInfo ) { // Convert materials info into normalized form based on options - var mat = materialsInfo[ mn ]; + const mat = materialsInfo[ mn ]; - var covmat = {}; + const covmat = {}; converted[ mn ] = covmat; - for ( var prop in mat ) { + for ( const prop in mat ) { - var save = true; - var value = mat[ prop ]; - var lprop = prop.toLowerCase(); + let save = true; + let value = mat[ prop ]; + const lprop = prop.toLowerCase(); switch ( lprop ) { @@ -278,29 +274,29 @@ MTLLoader.MaterialCreator.prototype = { return converted; - }, + } - preload: function () { + preload() { - for ( var mn in this.materialsInfo ) { + for ( const mn in this.materialsInfo ) { this.create( mn ); } - }, + } - getIndex: function ( materialName ) { + getIndex( materialName ) { return this.nameLookup[ materialName ]; - }, + } - getAsArray: function () { + getAsArray() { - var index = 0; + let index = 0; - for ( var mn in this.materialsInfo ) { + for ( const mn in this.materialsInfo ) { this.materialsArray[ index ] = this.create( mn ); this.nameLookup[ mn ] = index; @@ -310,9 +306,9 @@ MTLLoader.MaterialCreator.prototype = { return this.materialsArray; - }, + } - create: function ( materialName ) { + create( materialName ) { if ( this.materials[ materialName ] === undefined ) { @@ -322,15 +318,15 @@ MTLLoader.MaterialCreator.prototype = { return this.materials[ materialName ]; - }, + } - createMaterial_: function ( materialName ) { + createMaterial_( materialName ) { // Create material - var scope = this; - var mat = this.materialsInfo[ materialName ]; - var params = { + const scope = this; + const mat = this.materialsInfo[ materialName ]; + const params = { name: materialName, side: this.side @@ -353,8 +349,8 @@ MTLLoader.MaterialCreator.prototype = { if ( params[ mapType ] ) return; // Keep the first encountered texture - var texParams = scope.getTextureParams( value, params ); - var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); + const texParams = scope.getTextureParams( value, params ); + const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); map.repeat.copy( texParams.scale ); map.offset.copy( texParams.offset ); @@ -366,10 +362,10 @@ MTLLoader.MaterialCreator.prototype = { } - for ( var prop in mat ) { + for ( const prop in mat ) { - var value = mat[ prop ]; - var n; + const value = mat[ prop ]; + let n; if ( value === '' ) continue; @@ -492,19 +488,19 @@ MTLLoader.MaterialCreator.prototype = { this.materials[ materialName ] = new MeshPhongMaterial( params ); return this.materials[ materialName ]; - }, + } - getTextureParams: function ( value, matParams ) { + getTextureParams( value, matParams ) { - var texParams = { + const texParams = { scale: new Vector2( 1, 1 ), offset: new Vector2( 0, 0 ) }; - var items = value.split( /\s+/ ); - var pos; + const items = value.split( /\s+/ ); + let pos; pos = items.indexOf( '-bm' ); @@ -536,13 +532,12 @@ MTLLoader.MaterialCreator.prototype = { texParams.url = items.join( ' ' ).trim(); return texParams; - }, + } - loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { + loadTexture( url, mapping, onLoad, onProgress, onError ) { - var texture; - var manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager; - var loader = manager.getHandler( url ); + const manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager; + let loader = manager.getHandler( url ); if ( loader === null ) { @@ -551,7 +546,8 @@ MTLLoader.MaterialCreator.prototype = { } if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); - texture = loader.load( url, onLoad, onProgress, onError ); + + const texture = loader.load( url, onLoad, onProgress, onError ); if ( mapping !== undefined ) texture.mapping = mapping; @@ -559,6 +555,6 @@ MTLLoader.MaterialCreator.prototype = { } -}; +} export { MTLLoader }; diff --git a/examples/jsm/loaders/OBJLoader.js b/examples/jsm/loaders/OBJLoader.js index 6f2e4b50dec035..745ae0e315f114 100644 --- a/examples/jsm/loaders/OBJLoader.js +++ b/examples/jsm/loaders/OBJLoader.js @@ -14,678 +14,675 @@ import { Vector3 } from '../../../build/three.module.js'; -var OBJLoader = ( function () { +// o object_name | g group_name +const _object_pattern = /^[og]\s*(.+)?/; +// mtllib file_reference +const _material_library_pattern = /^mtllib /; +// usemtl material_name +const _material_use_pattern = /^usemtl /; +// usemap map_name +const _map_use_pattern = /^usemap /; - // o object_name | g group_name - var object_pattern = /^[og]\s*(.+)?/; - // mtllib file_reference - var material_library_pattern = /^mtllib /; - // usemtl material_name - var material_use_pattern = /^usemtl /; - // usemap map_name - var map_use_pattern = /^usemap /; +const _vA = new Vector3(); +const _vB = new Vector3(); +const _vC = new Vector3(); - var vA = new Vector3(); - var vB = new Vector3(); - var vC = new Vector3(); +const _ab = new Vector3(); +const _cb = new Vector3(); - var ab = new Vector3(); - var cb = new Vector3(); +function ParserState() { - function ParserState() { + const state = { + objects: [], + object: {}, - var state = { - objects: [], - object: {}, + vertices: [], + normals: [], + colors: [], + uvs: [], - vertices: [], - normals: [], - colors: [], - uvs: [], + materials: {}, + materialLibraries: [], - materials: {}, - materialLibraries: [], + startObject: function ( name, fromDeclaration ) { - startObject: function ( name, fromDeclaration ) { + // If the current object (initial from reset) is not from a g/o declaration in the parsed + // file. We need to use it for the first parsed g/o to keep things in sync. + if ( this.object && this.object.fromDeclaration === false ) { - // If the current object (initial from reset) is not from a g/o declaration in the parsed - // file. We need to use it for the first parsed g/o to keep things in sync. - if ( this.object && this.object.fromDeclaration === false ) { + this.object.name = name; + this.object.fromDeclaration = ( fromDeclaration !== false ); + return; - this.object.name = name; - this.object.fromDeclaration = ( fromDeclaration !== false ); - return; - - } - - var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); + } - if ( this.object && typeof this.object._finalize === 'function' ) { + const previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); - this.object._finalize( true ); + if ( this.object && typeof this.object._finalize === 'function' ) { - } + this.object._finalize( true ); - this.object = { - name: name || '', - fromDeclaration: ( fromDeclaration !== false ), + } - geometry: { - vertices: [], - normals: [], - colors: [], - uvs: [], - hasUVIndices: false - }, - materials: [], - smooth: true, + this.object = { + name: name || '', + fromDeclaration: ( fromDeclaration !== false ), - startMaterial: function ( name, libraries ) { + geometry: { + vertices: [], + normals: [], + colors: [], + uvs: [], + hasUVIndices: false + }, + materials: [], + smooth: true, - var previous = this._finalize( false ); + startMaterial: function ( name, libraries ) { - // New usemtl declaration overwrites an inherited material, except if faces were declared - // after the material, then it must be preserved for proper MultiMaterial continuation. - if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { + const previous = this._finalize( false ); - this.materials.splice( previous.index, 1 ); + // New usemtl declaration overwrites an inherited material, except if faces were declared + // after the material, then it must be preserved for proper MultiMaterial continuation. + if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { - } + this.materials.splice( previous.index, 1 ); - var material = { - index: this.materials.length, - name: name || '', - mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), - smooth: ( previous !== undefined ? previous.smooth : this.smooth ), - groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), - groupEnd: - 1, - groupCount: - 1, - inherited: false, - - clone: function ( index ) { - - var cloned = { - index: ( typeof index === 'number' ? index : this.index ), - name: this.name, - mtllib: this.mtllib, - smooth: this.smooth, - groupStart: 0, - groupEnd: - 1, - groupCount: - 1, - inherited: false - }; - cloned.clone = this.clone.bind( cloned ); - return cloned; + } - } - }; + const material = { + index: this.materials.length, + name: name || '', + mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), + smooth: ( previous !== undefined ? previous.smooth : this.smooth ), + groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), + groupEnd: - 1, + groupCount: - 1, + inherited: false, + + clone: function ( index ) { + + const cloned = { + index: ( typeof index === 'number' ? index : this.index ), + name: this.name, + mtllib: this.mtllib, + smooth: this.smooth, + groupStart: 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false + }; + cloned.clone = this.clone.bind( cloned ); + return cloned; - this.materials.push( material ); + } + }; - return material; + this.materials.push( material ); - }, + return material; - currentMaterial: function () { + }, - if ( this.materials.length > 0 ) { + currentMaterial: function () { - return this.materials[ this.materials.length - 1 ]; + if ( this.materials.length > 0 ) { - } + return this.materials[ this.materials.length - 1 ]; - return undefined; + } - }, + return undefined; - _finalize: function ( end ) { + }, - var lastMultiMaterial = this.currentMaterial(); - if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { + _finalize: function ( end ) { - lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; - lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; - lastMultiMaterial.inherited = false; + const lastMultiMaterial = this.currentMaterial(); + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { - } + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + lastMultiMaterial.inherited = false; - // Ignore objects tail materials if no face declarations followed them before a new o/g started. - if ( end && this.materials.length > 1 ) { + } - for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { + // Ignore objects tail materials if no face declarations followed them before a new o/g started. + if ( end && this.materials.length > 1 ) { - if ( this.materials[ mi ].groupCount <= 0 ) { + for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) { - this.materials.splice( mi, 1 ); + if ( this.materials[ mi ].groupCount <= 0 ) { - } + this.materials.splice( mi, 1 ); } } - // Guarantee at least one empty material, this makes the creation later more straight forward. - if ( end && this.materials.length === 0 ) { - - this.materials.push( { - name: '', - smooth: this.smooth - } ); + } - } + // Guarantee at least one empty material, this makes the creation later more straight forward. + if ( end && this.materials.length === 0 ) { - return lastMultiMaterial; + this.materials.push( { + name: '', + smooth: this.smooth + } ); } - }; - - // Inherit previous objects material. - // Spec tells us that a declared material must be set to all objects until a new material is declared. - // If a usemtl declaration is encountered while this new object is being parsed, it will - // overwrite the inherited material. Exception being that there was already face declarations - // to the inherited material, then it will be preserved for proper MultiMaterial continuation. - if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { - - var declared = previousMaterial.clone( 0 ); - declared.inherited = true; - this.object.materials.push( declared ); + return lastMultiMaterial; } + }; - this.objects.push( this.object ); + // Inherit previous objects material. + // Spec tells us that a declared material must be set to all objects until a new material is declared. + // If a usemtl declaration is encountered while this new object is being parsed, it will + // overwrite the inherited material. Exception being that there was already face declarations + // to the inherited material, then it will be preserved for proper MultiMaterial continuation. - }, + if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { - finalize: function () { + const declared = previousMaterial.clone( 0 ); + declared.inherited = true; + this.object.materials.push( declared ); - if ( this.object && typeof this.object._finalize === 'function' ) { + } - this.object._finalize( true ); + this.objects.push( this.object ); - } + }, - }, + finalize: function () { - parseVertexIndex: function ( value, len ) { + if ( this.object && typeof this.object._finalize === 'function' ) { - var index = parseInt( value, 10 ); - return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + this.object._finalize( true ); - }, + } - parseNormalIndex: function ( value, len ) { + }, - var index = parseInt( value, 10 ); - return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + parseVertexIndex: function ( value, len ) { - }, + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; - parseUVIndex: function ( value, len ) { + }, - var index = parseInt( value, 10 ); - return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; + parseNormalIndex: function ( value, len ) { - }, + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; - addVertex: function ( a, b, c ) { + }, - var src = this.vertices; - var dst = this.object.geometry.vertices; + parseUVIndex: function ( value, len ) { - dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); - dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; - }, + }, - addVertexPoint: function ( a ) { + addVertex: function ( a, b, c ) { - var src = this.vertices; - var dst = this.object.geometry.vertices; + const src = this.vertices; + const dst = this.object.geometry.vertices; - dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); - }, + }, - addVertexLine: function ( a ) { + addVertexPoint: function ( a ) { - var src = this.vertices; - var dst = this.object.geometry.vertices; + const src = this.vertices; + const dst = this.object.geometry.vertices; - dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - }, + }, - addNormal: function ( a, b, c ) { + addVertexLine: function ( a ) { - var src = this.normals; - var dst = this.object.geometry.normals; + const src = this.vertices; + const dst = this.object.geometry.vertices; - dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); - dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - }, + }, - addFaceNormal: function ( a, b, c ) { + addNormal: function ( a, b, c ) { - var src = this.vertices; - var dst = this.object.geometry.normals; + const src = this.normals; + const dst = this.object.geometry.normals; - vA.fromArray( src, a ); - vB.fromArray( src, b ); - vC.fromArray( src, c ); + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); - cb.subVectors( vC, vB ); - ab.subVectors( vA, vB ); - cb.cross( ab ); + }, - cb.normalize(); + addFaceNormal: function ( a, b, c ) { - dst.push( cb.x, cb.y, cb.z ); - dst.push( cb.x, cb.y, cb.z ); - dst.push( cb.x, cb.y, cb.z ); + const src = this.vertices; + const dst = this.object.geometry.normals; - }, + _vA.fromArray( src, a ); + _vB.fromArray( src, b ); + _vC.fromArray( src, c ); - addColor: function ( a, b, c ) { + _cb.subVectors( _vC, _vB ); + _ab.subVectors( _vA, _vB ); + _cb.cross( _ab ); - var src = this.colors; - var dst = this.object.geometry.colors; + _cb.normalize(); - if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); - if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); - if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + dst.push( _cb.x, _cb.y, _cb.z ); + dst.push( _cb.x, _cb.y, _cb.z ); + dst.push( _cb.x, _cb.y, _cb.z ); - }, + }, - addUV: function ( a, b, c ) { + addColor: function ( a, b, c ) { - var src = this.uvs; - var dst = this.object.geometry.uvs; + const src = this.colors; + const dst = this.object.geometry.colors; - dst.push( src[ a + 0 ], src[ a + 1 ] ); - dst.push( src[ b + 0 ], src[ b + 1 ] ); - dst.push( src[ c + 0 ], src[ c + 1 ] ); + if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); - }, + }, - addDefaultUV: function () { + addUV: function ( a, b, c ) { - var dst = this.object.geometry.uvs; + const src = this.uvs; + const dst = this.object.geometry.uvs; - dst.push( 0, 0 ); - dst.push( 0, 0 ); - dst.push( 0, 0 ); + dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ] ); - }, + }, - addUVLine: function ( a ) { + addDefaultUV: function () { - var src = this.uvs; - var dst = this.object.geometry.uvs; + const dst = this.object.geometry.uvs; - dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( 0, 0 ); + dst.push( 0, 0 ); + dst.push( 0, 0 ); - }, + }, - addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { + addUVLine: function ( a ) { - var vLen = this.vertices.length; + const src = this.uvs; + const dst = this.object.geometry.uvs; - var ia = this.parseVertexIndex( a, vLen ); - var ib = this.parseVertexIndex( b, vLen ); - var ic = this.parseVertexIndex( c, vLen ); + dst.push( src[ a + 0 ], src[ a + 1 ] ); - this.addVertex( ia, ib, ic ); - this.addColor( ia, ib, ic ); + }, - // normals + addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { - if ( na !== undefined && na !== '' ) { + const vLen = this.vertices.length; - var nLen = this.normals.length; + let ia = this.parseVertexIndex( a, vLen ); + let ib = this.parseVertexIndex( b, vLen ); + let ic = this.parseVertexIndex( c, vLen ); - ia = this.parseNormalIndex( na, nLen ); - ib = this.parseNormalIndex( nb, nLen ); - ic = this.parseNormalIndex( nc, nLen ); + this.addVertex( ia, ib, ic ); + this.addColor( ia, ib, ic ); - this.addNormal( ia, ib, ic ); + // normals - } else { + if ( na !== undefined && na !== '' ) { - this.addFaceNormal( ia, ib, ic ); + const nLen = this.normals.length; - } + ia = this.parseNormalIndex( na, nLen ); + ib = this.parseNormalIndex( nb, nLen ); + ic = this.parseNormalIndex( nc, nLen ); - // uvs + this.addNormal( ia, ib, ic ); - if ( ua !== undefined && ua !== '' ) { + } else { - var uvLen = this.uvs.length; + this.addFaceNormal( ia, ib, ic ); - ia = this.parseUVIndex( ua, uvLen ); - ib = this.parseUVIndex( ub, uvLen ); - ic = this.parseUVIndex( uc, uvLen ); + } - this.addUV( ia, ib, ic ); + // uvs - this.object.geometry.hasUVIndices = true; + if ( ua !== undefined && ua !== '' ) { - } else { + const uvLen = this.uvs.length; - // add placeholder values (for inconsistent face definitions) + ia = this.parseUVIndex( ua, uvLen ); + ib = this.parseUVIndex( ub, uvLen ); + ic = this.parseUVIndex( uc, uvLen ); - this.addDefaultUV(); + this.addUV( ia, ib, ic ); - } + this.object.geometry.hasUVIndices = true; - }, + } else { - addPointGeometry: function ( vertices ) { + // add placeholder values (for inconsistent face definitions) - this.object.geometry.type = 'Points'; + this.addDefaultUV(); - var vLen = this.vertices.length; + } - for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + }, - var index = this.parseVertexIndex( vertices[ vi ], vLen ); + addPointGeometry: function ( vertices ) { - this.addVertexPoint( index ); - this.addColor( index ); + this.object.geometry.type = 'Points'; - } + const vLen = this.vertices.length; - }, + for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) { - addLineGeometry: function ( vertices, uvs ) { + const index = this.parseVertexIndex( vertices[ vi ], vLen ); - this.object.geometry.type = 'Line'; + this.addVertexPoint( index ); + this.addColor( index ); - var vLen = this.vertices.length; - var uvLen = this.uvs.length; + } - for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + }, - this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); + addLineGeometry: function ( vertices, uvs ) { - } + this.object.geometry.type = 'Line'; - for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { + const vLen = this.vertices.length; + const uvLen = this.uvs.length; - this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); + for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) { - } + this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); } - }; + for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { - state.startObject( '', false ); + this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); - return state; + } - } + } - // + }; - function OBJLoader( manager ) { + state.startObject( '', false ); - Loader.call( this, manager ); + return state; - this.materials = null; +} - } +// - OBJLoader.prototype = Object.assign( Object.create( Loader.prototype ), { +class OBJLoader extends Loader { - constructor: OBJLoader, + constructor( manager ) { - load: function ( url, onLoad, onProgress, onError ) { + super( manager ); - var scope = this; + this.materials = null; - var loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { + } - try { + load( url, onLoad, onProgress, onError ) { - onLoad( scope.parse( text ) ); + const scope = this; - } catch ( e ) { + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { - if ( onError ) { + try { - onError( e ); + onLoad( scope.parse( text ) ); - } else { + } catch ( e ) { - console.error( e ); + if ( onError ) { - } + onError( e ); - scope.manager.itemError( url ); + } else { + + console.error( e ); } - }, onProgress, onError ); + scope.manager.itemError( url ); - }, + } - setMaterials: function ( materials ) { + }, onProgress, onError ); - this.materials = materials; + } - return this; + setMaterials( materials ) { - }, + this.materials = materials; - parse: function ( text ) { + return this; - var state = new ParserState(); + } - if ( text.indexOf( '\r\n' ) !== - 1 ) { + parse( text ) { - // This is faster than String.split with regex that splits on both - text = text.replace( /\r\n/g, '\n' ); + const state = new ParserState(); - } + if ( text.indexOf( '\r\n' ) !== - 1 ) { - if ( text.indexOf( '\\\n' ) !== - 1 ) { + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); - // join lines separated by a line continuation character (\) - text = text.replace( /\\\n/g, '' ); + } - } + if ( text.indexOf( '\\\n' ) !== - 1 ) { - var lines = text.split( '\n' ); - var line = '', lineFirstChar = ''; - var lineLength = 0; - var result = []; + // join lines separated by a line continuation character (\) + text = text.replace( /\\\n/g, '' ); - // Faster to just trim left side of the line. Use if available. - var trimLeft = ( typeof ''.trimLeft === 'function' ); + } - for ( var i = 0, l = lines.length; i < l; i ++ ) { + const lines = text.split( '\n' ); + let line = '', lineFirstChar = ''; + let lineLength = 0; + let result = []; - line = lines[ i ]; + // Faster to just trim left side of the line. Use if available. + const trimLeft = ( typeof ''.trimLeft === 'function' ); - line = trimLeft ? line.trimLeft() : line.trim(); + for ( let i = 0, l = lines.length; i < l; i ++ ) { - lineLength = line.length; + line = lines[ i ]; - if ( lineLength === 0 ) continue; + line = trimLeft ? line.trimLeft() : line.trim(); - lineFirstChar = line.charAt( 0 ); + lineLength = line.length; - // @todo invoke passed in handler if any - if ( lineFirstChar === '#' ) continue; + if ( lineLength === 0 ) continue; - if ( lineFirstChar === 'v' ) { + lineFirstChar = line.charAt( 0 ); - var data = line.split( /\s+/ ); + // @todo invoke passed in handler if any + if ( lineFirstChar === '#' ) continue; - switch ( data[ 0 ] ) { + if ( lineFirstChar === 'v' ) { - case 'v': - state.vertices.push( - parseFloat( data[ 1 ] ), - parseFloat( data[ 2 ] ), - parseFloat( data[ 3 ] ) - ); - if ( data.length >= 7 ) { + const data = line.split( /\s+/ ); - state.colors.push( - parseFloat( data[ 4 ] ), - parseFloat( data[ 5 ] ), - parseFloat( data[ 6 ] ) + switch ( data[ 0 ] ) { - ); + case 'v': + state.vertices.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + if ( data.length >= 7 ) { - } else { + state.colors.push( + parseFloat( data[ 4 ] ), + parseFloat( data[ 5 ] ), + parseFloat( data[ 6 ] ) - // if no colors are defined, add placeholders so color and vertex indices match + ); - state.colors.push( undefined, undefined, undefined ); + } else { - } + // if no colors are defined, add placeholders so color and vertex indices match - break; - case 'vn': - state.normals.push( - parseFloat( data[ 1 ] ), - parseFloat( data[ 2 ] ), - parseFloat( data[ 3 ] ) - ); - break; - case 'vt': - state.uvs.push( - parseFloat( data[ 1 ] ), - parseFloat( data[ 2 ] ) - ); - break; + state.colors.push( undefined, undefined, undefined ); - } + } + + break; + case 'vn': + state.normals.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + break; + case 'vt': + state.uvs.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ) + ); + break; - } else if ( lineFirstChar === 'f' ) { + } - var lineData = line.substr( 1 ).trim(); - var vertexData = lineData.split( /\s+/ ); - var faceVertices = []; + } else if ( lineFirstChar === 'f' ) { - // Parse the face vertex data into an easy to work with format + const lineData = line.substr( 1 ).trim(); + const vertexData = lineData.split( /\s+/ ); + const faceVertices = []; - for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { + // Parse the face vertex data into an easy to work with format - var vertex = vertexData[ j ]; + for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) { - if ( vertex.length > 0 ) { + const vertex = vertexData[ j ]; - var vertexParts = vertex.split( '/' ); - faceVertices.push( vertexParts ); + if ( vertex.length > 0 ) { - } + const vertexParts = vertex.split( '/' ); + faceVertices.push( vertexParts ); } - // Draw an edge between the first vertex and all subsequent vertices to form an n-gon + } - var v1 = faceVertices[ 0 ]; + // Draw an edge between the first vertex and all subsequent vertices to form an n-gon - for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { + const v1 = faceVertices[ 0 ]; - var v2 = faceVertices[ j ]; - var v3 = faceVertices[ j + 1 ]; + for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { - state.addFace( - v1[ 0 ], v2[ 0 ], v3[ 0 ], - v1[ 1 ], v2[ 1 ], v3[ 1 ], - v1[ 2 ], v2[ 2 ], v3[ 2 ] - ); + const v2 = faceVertices[ j ]; + const v3 = faceVertices[ j + 1 ]; - } + state.addFace( + v1[ 0 ], v2[ 0 ], v3[ 0 ], + v1[ 1 ], v2[ 1 ], v3[ 1 ], + v1[ 2 ], v2[ 2 ], v3[ 2 ] + ); - } else if ( lineFirstChar === 'l' ) { + } - var lineParts = line.substring( 1 ).trim().split( ' ' ); - var lineVertices = [], lineUVs = []; + } else if ( lineFirstChar === 'l' ) { - if ( line.indexOf( '/' ) === - 1 ) { + const lineParts = line.substring( 1 ).trim().split( ' ' ); + let lineVertices = []; + const lineUVs = []; - lineVertices = lineParts; + if ( line.indexOf( '/' ) === - 1 ) { - } else { + lineVertices = lineParts; - for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { + } else { - var parts = lineParts[ li ].split( '/' ); + for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) { - if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] ); - if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] ); + const parts = lineParts[ li ].split( '/' ); - } + if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] ); + if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] ); } - state.addLineGeometry( lineVertices, lineUVs ); + } - } else if ( lineFirstChar === 'p' ) { + state.addLineGeometry( lineVertices, lineUVs ); - var lineData = line.substr( 1 ).trim(); - var pointData = lineData.split( ' ' ); + } else if ( lineFirstChar === 'p' ) { - state.addPointGeometry( pointData ); + const lineData = line.substr( 1 ).trim(); + const pointData = lineData.split( ' ' ); - } else if ( ( result = object_pattern.exec( line ) ) !== null ) { + state.addPointGeometry( pointData ); - // o object_name - // or - // g group_name + } else if ( ( result = _object_pattern.exec( line ) ) !== null ) { - // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 - // var name = result[ 0 ].substr( 1 ).trim(); - var name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); + // o object_name + // or + // g group_name - state.startObject( name ); + // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 + // let name = result[ 0 ].substr( 1 ).trim(); + const name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); - } else if ( material_use_pattern.test( line ) ) { + state.startObject( name ); - // material + } else if ( _material_use_pattern.test( line ) ) { - state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); + // material - } else if ( material_library_pattern.test( line ) ) { + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); - // mtl file + } else if ( _material_library_pattern.test( line ) ) { - state.materialLibraries.push( line.substring( 7 ).trim() ); + // mtl file - } else if ( map_use_pattern.test( line ) ) { + state.materialLibraries.push( line.substring( 7 ).trim() ); - // the line is parsed but ignored since the loader assumes textures are defined MTL files - // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) + } else if ( _map_use_pattern.test( line ) ) { - console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' ); + // the line is parsed but ignored since the loader assumes textures are defined MTL files + // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) - } else if ( lineFirstChar === 's' ) { + console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' ); - result = line.split( ' ' ); + } else if ( lineFirstChar === 's' ) { - // smooth shading + result = line.split( ' ' ); - // @todo Handle files that have varying smooth values for a set of faces inside one geometry, - // but does not define a usemtl for each face set. - // This should be detected and a dummy material created (later MultiMaterial and geometry groups). - // This requires some care to not create extra material on each smooth value for "normal" obj files. - // where explicit usemtl defines geometry groups. - // Example asset: examples/models/obj/cerberus/Cerberus.obj + // smooth shading - /* + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj + + /* * http://paulbourke.net/dataformats/obj/ * or * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf @@ -696,223 +693,219 @@ var OBJLoader = ( function () { * surfaces, smoothing groups are either turned on or off; there is no difference between values greater * than 0." */ - if ( result.length > 1 ) { + if ( result.length > 1 ) { - var value = result[ 1 ].trim().toLowerCase(); - state.object.smooth = ( value !== '0' && value !== 'off' ); + const value = result[ 1 ].trim().toLowerCase(); + state.object.smooth = ( value !== '0' && value !== 'off' ); - } else { + } else { - // ZBrush can produce "s" lines #11707 - state.object.smooth = true; + // ZBrush can produce "s" lines #11707 + state.object.smooth = true; - } + } - var material = state.object.currentMaterial(); - if ( material ) material.smooth = state.object.smooth; + const material = state.object.currentMaterial(); + if ( material ) material.smooth = state.object.smooth; - } else { - - // Handle null terminated files without exception - if ( line === '\0' ) continue; + } else { - console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); + // Handle null terminated files without exception + if ( line === '\0' ) continue; - } + console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); } - state.finalize(); + } - var container = new Group(); - container.materialLibraries = [].concat( state.materialLibraries ); + state.finalize(); - var hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 ); + const container = new Group(); + container.materialLibraries = [].concat( state.materialLibraries ); - if ( hasPrimitives === true ) { + const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 ); - for ( var i = 0, l = state.objects.length; i < l; i ++ ) { + if ( hasPrimitives === true ) { - var object = state.objects[ i ]; - var geometry = object.geometry; - var materials = object.materials; - var isLine = ( geometry.type === 'Line' ); - var isPoints = ( geometry.type === 'Points' ); - var hasVertexColors = false; + for ( let i = 0, l = state.objects.length; i < l; i ++ ) { - // Skip o/g line declarations that did not follow with any faces - if ( geometry.vertices.length === 0 ) continue; + const object = state.objects[ i ]; + const geometry = object.geometry; + const materials = object.materials; + const isLine = ( geometry.type === 'Line' ); + const isPoints = ( geometry.type === 'Points' ); + let hasVertexColors = false; - var buffergeometry = new BufferGeometry(); + // Skip o/g line declarations that did not follow with any faces + if ( geometry.vertices.length === 0 ) continue; - buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) ); + const buffergeometry = new BufferGeometry(); - if ( geometry.normals.length > 0 ) { + buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) ); - buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) ); + if ( geometry.normals.length > 0 ) { - } + buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) ); - if ( geometry.colors.length > 0 ) { + } - hasVertexColors = true; - buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) ); + if ( geometry.colors.length > 0 ) { - } + hasVertexColors = true; + buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) ); - if ( geometry.hasUVIndices === true ) { + } - buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) ); + if ( geometry.hasUVIndices === true ) { - } + buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) ); - // Create materials + } - var createdMaterials = []; + // Create materials - for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + const createdMaterials = []; - var sourceMaterial = materials[ mi ]; - var materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; - var material = state.materials[ materialHash ]; + for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { - if ( this.materials !== null ) { + const sourceMaterial = materials[ mi ]; + const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; + let material = state.materials[ materialHash ]; - material = this.materials.create( sourceMaterial.name ); + if ( this.materials !== null ) { - // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. - if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) { + material = this.materials.create( sourceMaterial.name ); - var materialLine = new LineBasicMaterial(); - Material.prototype.copy.call( materialLine, material ); - materialLine.color.copy( material.color ); - material = materialLine; + // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) { - } else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) { + const materialLine = new LineBasicMaterial(); + Material.prototype.copy.call( materialLine, material ); + materialLine.color.copy( material.color ); + material = materialLine; - var materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } ); - Material.prototype.copy.call( materialPoints, material ); - materialPoints.color.copy( material.color ); - materialPoints.map = material.map; - material = materialPoints; + } else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) { - } + const materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } ); + Material.prototype.copy.call( materialPoints, material ); + materialPoints.color.copy( material.color ); + materialPoints.map = material.map; + material = materialPoints; } - if ( material === undefined ) { - - if ( isLine ) { - - material = new LineBasicMaterial(); + } - } else if ( isPoints ) { + if ( material === undefined ) { - material = new PointsMaterial( { size: 1, sizeAttenuation: false } ); + if ( isLine ) { - } else { + material = new LineBasicMaterial(); - material = new MeshPhongMaterial(); + } else if ( isPoints ) { - } + material = new PointsMaterial( { size: 1, sizeAttenuation: false } ); - material.name = sourceMaterial.name; - material.flatShading = sourceMaterial.smooth ? false : true; - material.vertexColors = hasVertexColors; + } else { - state.materials[ materialHash ] = material; + material = new MeshPhongMaterial(); } - createdMaterials.push( material ); + material.name = sourceMaterial.name; + material.flatShading = sourceMaterial.smooth ? false : true; + material.vertexColors = hasVertexColors; + + state.materials[ materialHash ] = material; } - // Create mesh + createdMaterials.push( material ); - var mesh; + } - if ( createdMaterials.length > 1 ) { + // Create mesh - for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + let mesh; - var sourceMaterial = materials[ mi ]; - buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + if ( createdMaterials.length > 1 ) { - } + for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { - if ( isLine ) { + const sourceMaterial = materials[ mi ]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); - mesh = new LineSegments( buffergeometry, createdMaterials ); + } - } else if ( isPoints ) { + if ( isLine ) { - mesh = new Points( buffergeometry, createdMaterials ); + mesh = new LineSegments( buffergeometry, createdMaterials ); - } else { + } else if ( isPoints ) { - mesh = new Mesh( buffergeometry, createdMaterials ); - - } + mesh = new Points( buffergeometry, createdMaterials ); } else { - if ( isLine ) { + mesh = new Mesh( buffergeometry, createdMaterials ); - mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] ); + } - } else if ( isPoints ) { + } else { - mesh = new Points( buffergeometry, createdMaterials[ 0 ] ); + if ( isLine ) { - } else { + mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] ); - mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] ); + } else if ( isPoints ) { - } + mesh = new Points( buffergeometry, createdMaterials[ 0 ] ); - } + } else { - mesh.name = object.name; + mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] ); - container.add( mesh ); + } } - } else { + mesh.name = object.name; - // if there is only the default parser state object with no geometry data, interpret data as point cloud + container.add( mesh ); - if ( state.vertices.length > 0 ) { + } - var material = new PointsMaterial( { size: 1, sizeAttenuation: false } ); + } else { - var buffergeometry = new BufferGeometry(); + // if there is only the default parser state object with no geometry data, interpret data as point cloud - buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) ); + if ( state.vertices.length > 0 ) { - if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) { + const material = new PointsMaterial( { size: 1, sizeAttenuation: false } ); - buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) ); - material.vertexColors = true; + const buffergeometry = new BufferGeometry(); - } + buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) ); - var points = new Points( buffergeometry, material ); - container.add( points ); + if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) { + + buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) ); + material.vertexColors = true; } - } + const points = new Points( buffergeometry, material ); + container.add( points ); - return container; + } } - } ); + return container; - return OBJLoader; + } -} )(); +} export { OBJLoader }; diff --git a/examples/jsm/loaders/PRWMLoader.js b/examples/jsm/loaders/PRWMLoader.js index 9b8b9560f03a23..46ed2bfee114c1 100644 --- a/examples/jsm/loaders/PRWMLoader.js +++ b/examples/jsm/loaders/PRWMLoader.js @@ -9,308 +9,291 @@ import { * See https://github.com/kchapelier/PRWM for more informations about this file format */ -var PRWMLoader = ( function () { +let bigEndianPlatform = null; - var bigEndianPlatform = null; - - /** +/** * Check if the endianness of the platform is big-endian (most significant bit first) * @returns {boolean} True if big-endian, false if little-endian */ - function isBigEndianPlatform() { +function isBigEndianPlatform() { - if ( bigEndianPlatform === null ) { + if ( bigEndianPlatform === null ) { - var buffer = new ArrayBuffer( 2 ), - uint8Array = new Uint8Array( buffer ), - uint16Array = new Uint16Array( buffer ); + const buffer = new ArrayBuffer( 2 ), + uint8Array = new Uint8Array( buffer ), + uint16Array = new Uint16Array( buffer ); - uint8Array[ 0 ] = 0xAA; // set first byte - uint8Array[ 1 ] = 0xBB; // set second byte - bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB ); + uint8Array[ 0 ] = 0xAA; // set first byte + uint8Array[ 1 ] = 0xBB; // set second byte + bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB ); - } + } - return bigEndianPlatform; + return bigEndianPlatform; - } +} - // match the values defined in the spec to the TypedArray types - var InvertedEncodingTypes = [ - null, - Float32Array, - null, - Int8Array, - Int16Array, - null, - Int32Array, - Uint8Array, - Uint16Array, - null, - Uint32Array - ]; - - // define the method to use on a DataView, corresponding the TypedArray type - var getMethods = { - Uint16Array: 'getUint16', - Uint32Array: 'getUint32', - Int16Array: 'getInt16', - Int32Array: 'getInt32', - Float32Array: 'getFloat32', - Float64Array: 'getFloat64' - }; +// match the values defined in the spec to the TypedArray types +const InvertedEncodingTypes = [ + null, + Float32Array, + null, + Int8Array, + Int16Array, + null, + Int32Array, + Uint8Array, + Uint16Array, + null, + Uint32Array +]; +// define the method to use on a DataView, corresponding the TypedArray type +const getMethods = { + Uint16Array: 'getUint16', + Uint32Array: 'getUint32', + Int16Array: 'getInt16', + Int32Array: 'getInt32', + Float32Array: 'getFloat32', + Float64Array: 'getFloat64' +}; - function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) { - var bytesPerElement = viewType.BYTES_PER_ELEMENT, - result; +function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) { - if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) { + const bytesPerElement = viewType.BYTES_PER_ELEMENT; + let result; - result = new viewType( sourceArrayBuffer, position, length ); + if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) { - } else { + result = new viewType( sourceArrayBuffer, position, length ); - var readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ), - getMethod = getMethods[ viewType.name ], - littleEndian = ! fromBigEndian, - i = 0; + } else { - result = new viewType( length ); + const readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ), + getMethod = getMethods[ viewType.name ], + littleEndian = ! fromBigEndian; - for ( ; i < length; i ++ ) { + result = new viewType( length ); - result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian ); + for ( let i = 0; i < length; i ++ ) { - } + result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian ); } - return result; - } + return result; - function decodePrwm( buffer ) { +} - var array = new Uint8Array( buffer ), - version = array[ 0 ], - flags = array[ 1 ], - indexedGeometry = !! ( flags >> 7 & 0x01 ), - indicesType = flags >> 6 & 0x01, - bigEndian = ( flags >> 5 & 0x01 ) === 1, - attributesNumber = flags & 0x1F, - valuesNumber = 0, - indicesNumber = 0; - if ( bigEndian ) { +function decodePrwm( buffer ) { - valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ]; - indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ]; + const array = new Uint8Array( buffer ), + version = array[ 0 ]; - } else { + let flags = array[ 1 ]; - valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 ); - indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 ); + const indexedGeometry = !! ( flags >> 7 & 0x01 ), + indicesType = flags >> 6 & 0x01, + bigEndian = ( flags >> 5 & 0x01 ) === 1, + attributesNumber = flags & 0x1F; - } + let valuesNumber = 0, + indicesNumber = 0; - /** PRELIMINARY CHECKS **/ + if ( bigEndian ) { - if ( version === 0 ) { + valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ]; + indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ]; - throw new Error( 'PRWM decoder: Invalid format version: 0' ); + } else { - } else if ( version !== 1 ) { + valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 ); + indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 ); - throw new Error( 'PRWM decoder: Unsupported format version: ' + version ); + } - } + /** PRELIMINARY CHECKS **/ - if ( ! indexedGeometry ) { + if ( version === 0 ) { - if ( indicesType !== 0 ) { + throw new Error( 'PRWM decoder: Invalid format version: 0' ); - throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' ); + } else if ( version !== 1 ) { - } else if ( indicesNumber !== 0 ) { + throw new Error( 'PRWM decoder: Unsupported format version: ' + version ); - throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' ); + } - } + if ( ! indexedGeometry ) { - } + if ( indicesType !== 0 ) { - /** PARSING **/ + throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' ); - var pos = 8; + } else if ( indicesNumber !== 0 ) { - var attributes = {}, - attributeName, - char, - attributeType, - cardinality, - encodingType, - arrayType, - values, - indices, - i; + throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' ); - for ( i = 0; i < attributesNumber; i ++ ) { + } - attributeName = ''; + } - while ( pos < array.length ) { + /** PARSING **/ - char = array[ pos ]; - pos ++; + let pos = 8; - if ( char === 0 ) { + const attributes = {}; - break; + for ( let i = 0; i < attributesNumber; i ++ ) { - } else { + let attributeName = ''; - attributeName += String.fromCharCode( char ); + while ( pos < array.length ) { - } + const char = array[ pos ]; + pos ++; - } + if ( char === 0 ) { - flags = array[ pos ]; + break; - attributeType = flags >> 7 & 0x01; - cardinality = ( flags >> 4 & 0x03 ) + 1; - encodingType = flags & 0x0F; - arrayType = InvertedEncodingTypes[ encodingType ]; + } else { - pos ++; + attributeName += String.fromCharCode( char ); - // padding to next multiple of 4 - pos = Math.ceil( pos / 4 ) * 4; + } - values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian ); + } - pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber; + flags = array[ pos ]; - attributes[ attributeName ] = { - type: attributeType, - cardinality: cardinality, - values: values - }; + const attributeType = flags >> 7 & 0x01; + const cardinality = ( flags >> 4 & 0x03 ) + 1; + const encodingType = flags & 0x0F; + const arrayType = InvertedEncodingTypes[ encodingType ]; - } + pos ++; + // padding to next multiple of 4 pos = Math.ceil( pos / 4 ) * 4; - indices = null; - - if ( indexedGeometry ) { + const values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian ); - indices = copyFromBuffer( - buffer, - indicesType === 1 ? Uint32Array : Uint16Array, - pos, - indicesNumber, - bigEndian - ); + pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber; - } - - return { - version: version, - attributes: attributes, - indices: indices + attributes[ attributeName ] = { + type: attributeType, + cardinality: cardinality, + values: values }; } - // Define the public interface + pos = Math.ceil( pos / 4 ) * 4; + + let indices = null; - function PRWMLoader( manager ) { + if ( indexedGeometry ) { - Loader.call( this, manager ); + indices = copyFromBuffer( + buffer, + indicesType === 1 ? Uint32Array : Uint16Array, + pos, + indicesNumber, + bigEndian + ); } - PRWMLoader.prototype = Object.assign( Object.create( Loader.prototype ), { + return { + version: version, + attributes: attributes, + indices: indices + }; + +} + +// Define the public interface - constructor: PRWMLoader, +class PRWMLoader extends Loader { - load: function ( url, onLoad, onProgress, onError ) { + constructor( manager ) { - var scope = this; + super( manager ); + + } - var loader = new FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); + load( url, onLoad, onProgress, onError ) { - url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' ); + const scope = this; - loader.load( url, function ( arrayBuffer ) { + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); - try { + url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' ); - onLoad( scope.parse( arrayBuffer ) ); + loader.load( url, function ( arrayBuffer ) { - } catch ( e ) { + try { - if ( onError ) { + onLoad( scope.parse( arrayBuffer ) ); - onError( e ); + } catch ( e ) { - } else { + if ( onError ) { - console.error( e ); + onError( e ); - } + } else { - scope.manager.itemError( url ); + console.error( e ); } - }, onProgress, onError ); + scope.manager.itemError( url ); - }, + } - parse: function ( arrayBuffer ) { + }, onProgress, onError ); - var data = decodePrwm( arrayBuffer ), - attributesKey = Object.keys( data.attributes ), - bufferGeometry = new BufferGeometry(), - attribute, - i; + } - for ( i = 0; i < attributesKey.length; i ++ ) { + parse( arrayBuffer ) { - attribute = data.attributes[ attributesKey[ i ] ]; - bufferGeometry.setAttribute( attributesKey[ i ], new BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) ); + const data = decodePrwm( arrayBuffer ), + attributesKey = Object.keys( data.attributes ), + bufferGeometry = new BufferGeometry(); - } + for ( let i = 0; i < attributesKey.length; i ++ ) { - if ( data.indices !== null ) { + const attribute = data.attributes[ attributesKey[ i ] ]; + bufferGeometry.setAttribute( attributesKey[ i ], new BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) ); - bufferGeometry.setIndex( new BufferAttribute( data.indices, 1 ) ); + } - } + if ( data.indices !== null ) { - return bufferGeometry; + bufferGeometry.setIndex( new BufferAttribute( data.indices, 1 ) ); } - } ); + return bufferGeometry; - PRWMLoader.isBigEndianPlatform = function () { + } - return isBigEndianPlatform(); + static isBigEndianPlatform() { - }; + return isBigEndianPlatform(); - return PRWMLoader; + } -} )(); +} export { PRWMLoader }; diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js index 3cf0284e457a84..36748be7b7b738 100644 --- a/examples/jsm/loaders/SVGLoader.js +++ b/examples/jsm/loaders/SVGLoader.js @@ -13,27 +13,25 @@ import { Vector3 } from '../../../build/three.module.js'; -var SVGLoader = function ( manager ) { +class SVGLoader extends Loader { - Loader.call( this, manager ); + constructor( manager ) { - // Default dots per inch - this.defaultDPI = 90; + super( manager ); - // Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px' - this.defaultUnit = 'px'; + // Default dots per inch + this.defaultDPI = 90; -}; + // Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px' + this.defaultUnit = 'px'; -SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { - - constructor: SVGLoader, + } - load: function ( url, onLoad, onProgress, onError ) { + load( url, onLoad, onProgress, onError ) { - var scope = this; + const scope = this; - var loader = new FileLoader( scope.manager ); + const loader = new FileLoader( scope.manager ); loader.setPath( scope.path ); loader.setRequestHeader( scope.requestHeader ); loader.setWithCredentials( scope.withCredentials ); @@ -61,21 +59,21 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { }, onProgress, onError ); - }, + } - parse: function ( text ) { + parse( text ) { - var scope = this; + const scope = this; function parseNode( node, style ) { if ( node.nodeType !== 1 ) return; - var transform = getNodeTransform( node ); + const transform = getNodeTransform( node ); - var traverseChildNodes = true; + let traverseChildNodes = true; - var path = null; + let path = null; switch ( node.nodeName ) { @@ -131,8 +129,8 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { case 'use': style = parseStyle( node, style ); - var usedNodeId = node.href.baseVal.substring( 1 ); - var usedNode = node.viewportElement.getElementById( usedNodeId ); + const usedNodeId = node.href.baseVal.substring( 1 ); + const usedNode = node.viewportElement.getElementById( usedNodeId ); if ( usedNode ) { parseNode( usedNode, style ); @@ -168,9 +166,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { if ( traverseChildNodes ) { - var nodes = node.childNodes; + const nodes = node.childNodes; - for ( var i = 0; i < nodes.length; i ++ ) { + for ( let i = 0; i < nodes.length; i ++ ) { parseNode( nodes[ i ], style ); @@ -198,27 +196,27 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function parsePathNode( node ) { - var path = new ShapePath(); + const path = new ShapePath(); - var point = new Vector2(); - var control = new Vector2(); + const point = new Vector2(); + const control = new Vector2(); - var firstPoint = new Vector2(); - var isFirstPoint = true; - var doSetFirstPoint = false; + const firstPoint = new Vector2(); + let isFirstPoint = true; + let doSetFirstPoint = false; - var d = node.getAttribute( 'd' ); + const d = node.getAttribute( 'd' ); // console.log( d ); - var commands = d.match( /[a-df-z][^a-df-z]*/ig ); + const commands = d.match( /[a-df-z][^a-df-z]*/ig ); - for ( var i = 0, l = commands.length; i < l; i ++ ) { + for ( let i = 0, l = commands.length; i < l; i ++ ) { - var command = commands[ i ]; + const command = commands[ i ]; - var type = command.charAt( 0 ); - var data = command.substr( 1 ).trim(); + const type = command.charAt( 0 ); + const data = command.substr( 1 ).trim(); if ( isFirstPoint === true ) { @@ -227,11 +225,13 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } + let numbers; + switch ( type ) { case 'M': - var numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + numbers = parseFloats( data ); + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { point.x = numbers[ j + 0 ]; point.y = numbers[ j + 1 ]; @@ -255,9 +255,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'H': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j ++ ) { + for ( let j = 0, jl = numbers.length; j < jl; j ++ ) { point.x = numbers[ j ]; control.x = point.x; @@ -271,9 +271,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'V': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j ++ ) { + for ( let j = 0, jl = numbers.length; j < jl; j ++ ) { point.y = numbers[ j ]; control.x = point.x; @@ -287,9 +287,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'L': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { point.x = numbers[ j + 0 ]; point.y = numbers[ j + 1 ]; @@ -304,9 +304,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'C': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 6 ) { path.bezierCurveTo( numbers[ j + 0 ], @@ -328,9 +328,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'S': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 4 ) { path.bezierCurveTo( getReflection( point.x, control.x ), @@ -352,9 +352,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'Q': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 4 ) { path.quadraticCurveTo( numbers[ j + 0 ], @@ -374,12 +374,12 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'T': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { - var rx = getReflection( point.x, control.x ); - var ry = getReflection( point.y, control.y ); + const rx = getReflection( point.x, control.x ); + const ry = getReflection( point.y, control.y ); path.quadraticCurveTo( rx, ry, @@ -398,14 +398,14 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'A': - var numbers = parseFloats( data, [ 3, 4 ], 7 ); + numbers = parseFloats( data, [ 3, 4 ], 7 ); - for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 7 ) { // skip command if start point == end point if ( numbers[ j + 5 ] == point.x && numbers[ j + 6 ] == point.y ) continue; - var start = point.clone(); + const start = point.clone(); point.x = numbers[ j + 5 ]; point.y = numbers[ j + 6 ]; control.x = point.x; @@ -421,9 +421,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'm': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { point.x += numbers[ j + 0 ]; point.y += numbers[ j + 1 ]; @@ -447,9 +447,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'h': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j ++ ) { + for ( let j = 0, jl = numbers.length; j < jl; j ++ ) { point.x += numbers[ j ]; control.x = point.x; @@ -463,9 +463,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'v': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j ++ ) { + for ( let j = 0, jl = numbers.length; j < jl; j ++ ) { point.y += numbers[ j ]; control.x = point.x; @@ -479,9 +479,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'l': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { point.x += numbers[ j + 0 ]; point.y += numbers[ j + 1 ]; @@ -496,9 +496,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'c': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 6 ) { path.bezierCurveTo( point.x + numbers[ j + 0 ], @@ -520,9 +520,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 's': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 4 ) { path.bezierCurveTo( getReflection( point.x, control.x ), @@ -544,9 +544,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'q': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 4 ) { path.quadraticCurveTo( point.x + numbers[ j + 0 ], @@ -566,12 +566,12 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 't': - var numbers = parseFloats( data ); + numbers = parseFloats( data ); - for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 2 ) { - var rx = getReflection( point.x, control.x ); - var ry = getReflection( point.y, control.y ); + const rx = getReflection( point.x, control.x ); + const ry = getReflection( point.y, control.y ); path.quadraticCurveTo( rx, ry, @@ -590,14 +590,14 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { break; case 'a': - var numbers = parseFloats( data, [ 3, 4 ], 7 ); + numbers = parseFloats( data, [ 3, 4 ], 7 ); - for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) { + for ( let j = 0, jl = numbers.length; j < jl; j += 7 ) { // skip command if no displacement if ( numbers[ j + 5 ] == 0 && numbers[ j + 6 ] == 0 ) continue; - var start = point.clone(); + const start = point.clone(); point.x += numbers[ j + 5 ]; point.y += numbers[ j + 6 ]; control.x = point.x; @@ -646,18 +646,18 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { if ( ! node.sheet || ! node.sheet.cssRules || ! node.sheet.cssRules.length ) return; - for ( var i = 0; i < node.sheet.cssRules.length; i ++ ) { + for ( let i = 0; i < node.sheet.cssRules.length; i ++ ) { - var stylesheet = node.sheet.cssRules[ i ]; + const stylesheet = node.sheet.cssRules[ i ]; if ( stylesheet.type !== 1 ) continue; - var selectorList = stylesheet.selectorText + const selectorList = stylesheet.selectorText .split( /,/gm ) .filter( Boolean ) .map( i => i.trim() ); - for ( var j = 0; j < selectorList.length; j ++ ) { + for ( let j = 0; j < selectorList.length; j ++ ) { stylesheets[ selectorList[ j ] ] = Object.assign( stylesheets[ selectorList[ j ] ] || {}, @@ -696,24 +696,24 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { ry = Math.abs( ry ); // Compute (x1', y1') - var dx2 = ( start.x - end.x ) / 2.0; - var dy2 = ( start.y - end.y ) / 2.0; - var x1p = Math.cos( x_axis_rotation ) * dx2 + Math.sin( x_axis_rotation ) * dy2; - var y1p = - Math.sin( x_axis_rotation ) * dx2 + Math.cos( x_axis_rotation ) * dy2; + const dx2 = ( start.x - end.x ) / 2.0; + const dy2 = ( start.y - end.y ) / 2.0; + const x1p = Math.cos( x_axis_rotation ) * dx2 + Math.sin( x_axis_rotation ) * dy2; + const y1p = - Math.sin( x_axis_rotation ) * dx2 + Math.cos( x_axis_rotation ) * dy2; // Compute (cx', cy') - var rxs = rx * rx; - var rys = ry * ry; - var x1ps = x1p * x1p; - var y1ps = y1p * y1p; + let rxs = rx * rx; + let rys = ry * ry; + const x1ps = x1p * x1p; + const y1ps = y1p * y1p; // Ensure radii are large enough - var cr = x1ps / rxs + y1ps / rys; + const cr = x1ps / rxs + y1ps / rys; if ( cr > 1 ) { // scale up rx,ry equally so cr == 1 - var s = Math.sqrt( cr ); + const s = Math.sqrt( cr ); rx = s * rx; ry = s * ry; rxs = rx * rx; @@ -721,20 +721,20 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var dq = ( rxs * y1ps + rys * x1ps ); - var pq = ( rxs * rys - dq ) / dq; - var q = Math.sqrt( Math.max( 0, pq ) ); + const dq = ( rxs * y1ps + rys * x1ps ); + const pq = ( rxs * rys - dq ) / dq; + let q = Math.sqrt( Math.max( 0, pq ) ); if ( large_arc_flag === sweep_flag ) q = - q; - var cxp = q * rx * y1p / ry; - var cyp = - q * ry * x1p / rx; + const cxp = q * rx * y1p / ry; + const cyp = - q * ry * x1p / rx; // Step 3: Compute (cx, cy) from (cx', cy') - var cx = Math.cos( x_axis_rotation ) * cxp - Math.sin( x_axis_rotation ) * cyp + ( start.x + end.x ) / 2; - var cy = Math.sin( x_axis_rotation ) * cxp + Math.cos( x_axis_rotation ) * cyp + ( start.y + end.y ) / 2; + const cx = Math.cos( x_axis_rotation ) * cxp - Math.sin( x_axis_rotation ) * cyp + ( start.x + end.x ) / 2; + const cy = Math.sin( x_axis_rotation ) * cxp + Math.cos( x_axis_rotation ) * cyp + ( start.y + end.y ) / 2; // Step 4: Compute θ1 and Δθ - var theta = svgAngle( 1, 0, ( x1p - cxp ) / rx, ( y1p - cyp ) / ry ); - var delta = svgAngle( ( x1p - cxp ) / rx, ( y1p - cyp ) / ry, ( - x1p - cxp ) / rx, ( - y1p - cyp ) / ry ) % ( Math.PI * 2 ); + const theta = svgAngle( 1, 0, ( x1p - cxp ) / rx, ( y1p - cyp ) / ry ); + const delta = svgAngle( ( x1p - cxp ) / rx, ( y1p - cyp ) / ry, ( - x1p - cxp ) / rx, ( - y1p - cyp ) / ry ) % ( Math.PI * 2 ); path.currentPath.absellipse( cx, cy, rx, ry, theta, theta + delta, sweep_flag === 0, x_axis_rotation ); @@ -742,9 +742,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function svgAngle( ux, uy, vx, vy ) { - var dot = ux * vx + uy * vy; - var len = Math.sqrt( ux * ux + uy * uy ) * Math.sqrt( vx * vx + vy * vy ); - var ang = Math.acos( Math.max( - 1, Math.min( 1, dot / len ) ) ); // floating point precision, slightly over values appear + const dot = ux * vx + uy * vy; + const len = Math.sqrt( ux * ux + uy * uy ) * Math.sqrt( vx * vx + vy * vy ); + let ang = Math.acos( Math.max( - 1, Math.min( 1, dot / len ) ) ); // floating point precision, slightly over values appear if ( ( ux * vy - uy * vx ) < 0 ) ang = - ang; return ang; @@ -756,14 +756,14 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { */ function parseRectNode( node ) { - var x = parseFloatWithUnits( node.getAttribute( 'x' ) || 0 ); - var y = parseFloatWithUnits( node.getAttribute( 'y' ) || 0 ); - var rx = parseFloatWithUnits( node.getAttribute( 'rx' ) || 0 ); - var ry = parseFloatWithUnits( node.getAttribute( 'ry' ) || 0 ); - var w = parseFloatWithUnits( node.getAttribute( 'width' ) ); - var h = parseFloatWithUnits( node.getAttribute( 'height' ) ); + const x = parseFloatWithUnits( node.getAttribute( 'x' ) || 0 ); + const y = parseFloatWithUnits( node.getAttribute( 'y' ) || 0 ); + const rx = parseFloatWithUnits( node.getAttribute( 'rx' ) || 0 ); + const ry = parseFloatWithUnits( node.getAttribute( 'ry' ) || 0 ); + const w = parseFloatWithUnits( node.getAttribute( 'width' ) ); + const h = parseFloatWithUnits( node.getAttribute( 'height' ) ); - var path = new ShapePath(); + const path = new ShapePath(); path.moveTo( x + 2 * rx, y ); path.lineTo( x + w - 2 * rx, y ); if ( rx !== 0 || ry !== 0 ) path.bezierCurveTo( x + w, y, x + w, y, x + w, y + 2 * ry ); @@ -793,8 +793,8 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function iterator( match, a, b ) { - var x = parseFloatWithUnits( a ); - var y = parseFloatWithUnits( b ); + const x = parseFloatWithUnits( a ); + const y = parseFloatWithUnits( b ); if ( index === 0 ) { @@ -810,11 +810,11 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; + const regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; - var path = new ShapePath(); + const path = new ShapePath(); - var index = 0; + let index = 0; node.getAttribute( 'points' ).replace( regex, iterator ); @@ -828,8 +828,8 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function iterator( match, a, b ) { - var x = parseFloatWithUnits( a ); - var y = parseFloatWithUnits( b ); + const x = parseFloatWithUnits( a ); + const y = parseFloatWithUnits( b ); if ( index === 0 ) { @@ -845,11 +845,11 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; + const regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; - var path = new ShapePath(); + const path = new ShapePath(); - var index = 0; + let index = 0; node.getAttribute( 'points' ).replace( regex, iterator ); @@ -861,14 +861,14 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function parseCircleNode( node ) { - var x = parseFloatWithUnits( node.getAttribute( 'cx' ) || 0 ); - var y = parseFloatWithUnits( node.getAttribute( 'cy' ) || 0 ); - var r = parseFloatWithUnits( node.getAttribute( 'r' ) || 0 ); + const x = parseFloatWithUnits( node.getAttribute( 'cx' ) || 0 ); + const y = parseFloatWithUnits( node.getAttribute( 'cy' ) || 0 ); + const r = parseFloatWithUnits( node.getAttribute( 'r' ) || 0 ); - var subpath = new Path(); + const subpath = new Path(); subpath.absarc( x, y, r, 0, Math.PI * 2 ); - var path = new ShapePath(); + const path = new ShapePath(); path.subPaths.push( subpath ); return path; @@ -877,15 +877,15 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function parseEllipseNode( node ) { - var x = parseFloatWithUnits( node.getAttribute( 'cx' ) || 0 ); - var y = parseFloatWithUnits( node.getAttribute( 'cy' ) || 0 ); - var rx = parseFloatWithUnits( node.getAttribute( 'rx' ) || 0 ); - var ry = parseFloatWithUnits( node.getAttribute( 'ry' ) || 0 ); + const x = parseFloatWithUnits( node.getAttribute( 'cx' ) || 0 ); + const y = parseFloatWithUnits( node.getAttribute( 'cy' ) || 0 ); + const rx = parseFloatWithUnits( node.getAttribute( 'rx' ) || 0 ); + const ry = parseFloatWithUnits( node.getAttribute( 'ry' ) || 0 ); - var subpath = new Path(); + const subpath = new Path(); subpath.absellipse( x, y, rx, ry, 0, Math.PI * 2 ); - var path = new ShapePath(); + const path = new ShapePath(); path.subPaths.push( subpath ); return path; @@ -894,12 +894,12 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function parseLineNode( node ) { - var x1 = parseFloatWithUnits( node.getAttribute( 'x1' ) || 0 ); - var y1 = parseFloatWithUnits( node.getAttribute( 'y1' ) || 0 ); - var x2 = parseFloatWithUnits( node.getAttribute( 'x2' ) || 0 ); - var y2 = parseFloatWithUnits( node.getAttribute( 'y2' ) || 0 ); + const x1 = parseFloatWithUnits( node.getAttribute( 'x1' ) || 0 ); + const y1 = parseFloatWithUnits( node.getAttribute( 'y1' ) || 0 ); + const x2 = parseFloatWithUnits( node.getAttribute( 'x2' ) || 0 ); + const y2 = parseFloatWithUnits( node.getAttribute( 'y2' ) || 0 ); - var path = new ShapePath(); + const path = new ShapePath(); path.moveTo( x1, y1 ); path.lineTo( x2, y2 ); path.currentPath.autoClose = false; @@ -914,16 +914,16 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { style = Object.assign( {}, style ); // clone style - var stylesheetStyles = {}; + let stylesheetStyles = {}; if ( node.hasAttribute( 'class' ) ) { - var classSelectors = node.getAttribute( 'class' ) + const classSelectors = node.getAttribute( 'class' ) .split( /\s/ ) .filter( Boolean ) .map( i => i.trim() ); - for ( var i = 0; i < classSelectors.length; i ++ ) { + for ( let i = 0; i < classSelectors.length; i ++ ) { stylesheetStyles = Object.assign( stylesheetStyles, stylesheets[ '.' + classSelectors[ i ] ] ); @@ -999,7 +999,7 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } // Character groups - var RE = { + const RE = { SEPARATOR: /[ \t\r\n\,.\-+]/, WHITESPACE: /[ \t\r\n]/, DIGIT: /[\d]/, @@ -1011,18 +1011,19 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { }; // States - var SEP = 0; - var INT = 1; - var FLOAT = 2; - var EXP = 3; + const SEP = 0; + const INT = 1; + const FLOAT = 2; + const EXP = 3; - var state = SEP; - var seenComma = true; - var result = [], number = '', exponent = ''; + let state = SEP; + let seenComma = true; + let number = '', exponent = ''; + const result = []; function throwSyntaxError( current, i, partial ) { - var error = new SyntaxError( 'Unexpected character "' + current + '" at index ' + i + '.' ); + const error = new SyntaxError( 'Unexpected character "' + current + '" at index ' + i + '.' ); error.partial = partial; throw error; @@ -1042,8 +1043,10 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var current, i = 0, length = input.length; - for ( i = 0; i < length; i ++ ) { + let current; + const length = input.length; + + for ( let i = 0; i < length; i ++ ) { current = input[ i ]; @@ -1233,10 +1236,10 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { // Units - var units = [ 'mm', 'cm', 'in', 'pt', 'pc', 'px' ]; + const units = [ 'mm', 'cm', 'in', 'pt', 'pc', 'px' ]; // Conversion: [ fromUnit ][ toUnit ] (-1 means dpi dependent) - var unitConversion = { + const unitConversion = { 'mm': { 'mm': 1, @@ -1286,13 +1289,13 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function parseFloatWithUnits( string ) { - var theUnit = 'px'; + let theUnit = 'px'; if ( typeof string === 'string' || string instanceof String ) { - for ( var i = 0, n = units.length; i < n; i ++ ) { + for ( let i = 0, n = units.length; i < n; i ++ ) { - var u = units[ i ]; + const u = units[ i ]; if ( string.endsWith( u ) ) { @@ -1306,7 +1309,7 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var scale = undefined; + let scale = undefined; if ( theUnit === 'px' && scope.defaultUnit !== 'px' ) { @@ -1342,7 +1345,7 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var transform = parseNodeTransform( node ); + const transform = parseNodeTransform( node ); if ( transformStack.length > 0 ) { @@ -1359,13 +1362,13 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function parseNodeTransform( node ) { - var transform = new Matrix3(); - var currentTransform = tempTransform0; + const transform = new Matrix3(); + const currentTransform = tempTransform0; if ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) { - var tx = parseFloatWithUnits( node.getAttribute( 'x' ) ); - var ty = parseFloatWithUnits( node.getAttribute( 'y' ) ); + const tx = parseFloatWithUnits( node.getAttribute( 'x' ) ); + const ty = parseFloatWithUnits( node.getAttribute( 'y' ) ); transform.translate( tx, ty ); @@ -1373,22 +1376,22 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { if ( node.hasAttribute( 'transform' ) ) { - var transformsTexts = node.getAttribute( 'transform' ).split( ')' ); + const transformsTexts = node.getAttribute( 'transform' ).split( ')' ); - for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { + for ( let tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { - var transformText = transformsTexts[ tIndex ].trim(); + const transformText = transformsTexts[ tIndex ].trim(); if ( transformText === '' ) continue; - var openParPos = transformText.indexOf( '(' ); - var closeParPos = transformText.length; + const openParPos = transformText.indexOf( '(' ); + const closeParPos = transformText.length; if ( openParPos > 0 && openParPos < closeParPos ) { - var transformType = transformText.substr( 0, openParPos ); + const transformType = transformText.substr( 0, openParPos ); - var array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) ); + const array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) ); currentTransform.identity(); @@ -1398,8 +1401,8 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { if ( array.length >= 1 ) { - var tx = array[ 0 ]; - var ty = tx; + const tx = array[ 0 ]; + let ty = tx; if ( array.length >= 2 ) { @@ -1417,9 +1420,9 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { if ( array.length >= 1 ) { - var angle = 0; - var cx = 0; - var cy = 0; + let angle = 0; + let cx = 0; + let cy = 0; // Angle angle = - array[ 0 ] * Math.PI / 180; @@ -1447,8 +1450,8 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { if ( array.length >= 1 ) { - var scaleX = array[ 0 ]; - var scaleY = scaleX; + const scaleX = array[ 0 ]; + let scaleY = scaleX; if ( array.length >= 2 ) { @@ -1528,18 +1531,18 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var isRotated = isTransformRotated( m ); + const isRotated = isTransformRotated( m ); - var subPaths = path.subPaths; + const subPaths = path.subPaths; - for ( var i = 0, n = subPaths.length; i < n; i ++ ) { + for ( let i = 0, n = subPaths.length; i < n; i ++ ) { - var subPath = subPaths[ i ]; - var curves = subPath.curves; + const subPath = subPaths[ i ]; + const curves = subPath.curves; - for ( var j = 0; j < curves.length; j ++ ) { + for ( let j = 0; j < curves.length; j ++ ) { - var curve = curves[ j ]; + const curve = curves[ j ]; if ( curve.isLineCurve ) { @@ -1591,35 +1594,35 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function getTransformScaleX( m ) { - var te = m.elements; + const te = m.elements; return Math.sqrt( te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] ); } function getTransformScaleY( m ) { - var te = m.elements; + const te = m.elements; return Math.sqrt( te[ 3 ] * te[ 3 ] + te[ 4 ] * te[ 4 ] ); } // - var paths = []; - var stylesheets = {}; + const paths = []; + const stylesheets = {}; - var transformStack = []; + const transformStack = []; - var tempTransform0 = new Matrix3(); - var tempTransform1 = new Matrix3(); - var tempTransform2 = new Matrix3(); - var tempTransform3 = new Matrix3(); - var tempV2 = new Vector2(); - var tempV3 = new Vector3(); + const tempTransform0 = new Matrix3(); + const tempTransform1 = new Matrix3(); + const tempTransform2 = new Matrix3(); + const tempTransform3 = new Matrix3(); + const tempV2 = new Vector2(); + const tempV3 = new Vector3(); - var currentTransform = new Matrix3(); + const currentTransform = new Matrix3(); - var xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml + const xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml parseNode( xml.documentElement, { fill: '#000', @@ -1631,527 +1634,505 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { strokeMiterLimit: 4 } ); - var data = { paths: paths, xml: xml.documentElement }; + const data = { paths: paths, xml: xml.documentElement }; // console.log( paths ); return data; } -} ); + static createShapes( shapePath ) { -SVGLoader.createShapes = function ( shapePath ) { + // Param shapePath: a shapepath as returned by the parse function of this class + // Returns Shape object - // Param shapePath: a shapepath as returned by the parse function of this class - // Returns Shape object + const BIGNUMBER = 999999999; - const BIGNUMBER = 999999999; + const IntersectionLocationType = { + ORIGIN: 0, + DESTINATION: 1, + BETWEEN: 2, + LEFT: 3, + RIGHT: 4, + BEHIND: 5, + BEYOND: 6 + }; - const IntersectionLocationType = { - ORIGIN: 0, - DESTINATION: 1, - BETWEEN: 2, - LEFT: 3, - RIGHT: 4, - BEHIND: 5, - BEYOND: 6 - }; + const classifyResult = { + loc: IntersectionLocationType.ORIGIN, + t: 0 + }; - const classifyResult = { - loc: IntersectionLocationType.ORIGIN, - t: 0 - }; + function findEdgeIntersection( a0, a1, b0, b1 ) { - function findEdgeIntersection( a0, a1, b0, b1 ) { + const x1 = a0.x; + const x2 = a1.x; + const x3 = b0.x; + const x4 = b1.x; + const y1 = a0.y; + const y2 = a1.y; + const y3 = b0.y; + const y4 = b1.y; + const nom1 = ( x4 - x3 ) * ( y1 - y3 ) - ( y4 - y3 ) * ( x1 - x3 ); + const nom2 = ( x2 - x1 ) * ( y1 - y3 ) - ( y2 - y1 ) * ( x1 - x3 ); + const denom = ( y4 - y3 ) * ( x2 - x1 ) - ( x4 - x3 ) * ( y2 - y1 ); + const t1 = nom1 / denom; + const t2 = nom2 / denom; - var x1 = a0.x; - var x2 = a1.x; - var x3 = b0.x; - var x4 = b1.x; - var y1 = a0.y; - var y2 = a1.y; - var y3 = b0.y; - var y4 = b1.y; - var nom1 = ( x4 - x3 ) * ( y1 - y3 ) - ( y4 - y3 ) * ( x1 - x3 ); - var nom2 = ( x2 - x1 ) * ( y1 - y3 ) - ( y2 - y1 ) * ( x1 - x3 ); - var denom = ( y4 - y3 ) * ( x2 - x1 ) - ( x4 - x3 ) * ( y2 - y1 ); - var t1 = nom1 / denom; - var t2 = nom2 / denom; + if ( ( ( denom === 0 ) && ( nom1 !== 0 ) ) || ( t1 <= 0 ) || ( t1 >= 1 ) || ( t2 < 0 ) || ( t2 > 1 ) ) { - if ( ( ( denom === 0 ) && ( nom1 !== 0 ) ) || ( t1 <= 0 ) || ( t1 >= 1 ) || ( t2 < 0 ) || ( t2 > 1 ) ) { + //1. lines are parallel or edges don't intersect - //1. lines are parallel or edges don't intersect + return null; - return null; + } else if ( ( nom1 === 0 ) && ( denom === 0 ) ) { - } else if ( ( nom1 === 0 ) && ( denom === 0 ) ) { + //2. lines are colinear - //2. lines are colinear + //check if endpoints of edge2 (b0-b1) lies on edge1 (a0-a1) + for ( let i = 0; i < 2; i ++ ) { - //check if endpoints of edge2 (b0-b1) lies on edge1 (a0-a1) - for ( var i = 0; i < 2; i ++ ) { + classifyPoint( i === 0 ? b0 : b1, a0, a1 ); + //find position of this endpoints relatively to edge1 + if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { - classifyPoint( i === 0 ? b0 : b1, a0, a1 ); - //find position of this endpoints relatively to edge1 - if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { + const point = ( i === 0 ? b0 : b1 ); + return { x: point.x, y: point.y, t: classifyResult.t }; - var point = ( i === 0 ? b0 : b1 ); - return { x: point.x, y: point.y, t: classifyResult.t }; + } else if ( classifyResult.loc == IntersectionLocationType.BETWEEN ) { - } else if ( classifyResult.loc == IntersectionLocationType.BETWEEN ) { + const x = + ( ( x1 + classifyResult.t * ( x2 - x1 ) ).toPrecision( 10 ) ); + const y = + ( ( y1 + classifyResult.t * ( y2 - y1 ) ).toPrecision( 10 ) ); + return { x: x, y: y, t: classifyResult.t, }; - var x = + ( ( x1 + classifyResult.t * ( x2 - x1 ) ).toPrecision( 10 ) ); - var y = + ( ( y1 + classifyResult.t * ( y2 - y1 ) ).toPrecision( 10 ) ); - return { x: x, y: y, t: classifyResult.t, }; + } } - } + return null; - return null; + } else { - } else { + //3. edges intersect - //3. edges intersect + for ( let i = 0; i < 2; i ++ ) { - for ( var i = 0; i < 2; i ++ ) { + classifyPoint( i === 0 ? b0 : b1, a0, a1 ); - classifyPoint( i === 0 ? b0 : b1, a0, a1 ); + if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { - if ( classifyResult.loc == IntersectionLocationType.ORIGIN ) { + const point = ( i === 0 ? b0 : b1 ); + return { x: point.x, y: point.y, t: classifyResult.t }; - var point = ( i === 0 ? b0 : b1 ); - return { x: point.x, y: point.y, t: classifyResult.t }; + } } - } + const x = + ( ( x1 + t1 * ( x2 - x1 ) ).toPrecision( 10 ) ); + const y = + ( ( y1 + t1 * ( y2 - y1 ) ).toPrecision( 10 ) ); + return { x: x, y: y, t: t1 }; - var x = + ( ( x1 + t1 * ( x2 - x1 ) ).toPrecision( 10 ) ); - var y = + ( ( y1 + t1 * ( y2 - y1 ) ).toPrecision( 10 ) ); - return { x: x, y: y, t: t1 }; + } } - } + function classifyPoint( p, edgeStart, edgeEnd ) { - function classifyPoint( p, edgeStart, edgeEnd ) { + const ax = edgeEnd.x - edgeStart.x; + const ay = edgeEnd.y - edgeStart.y; + const bx = p.x - edgeStart.x; + const by = p.y - edgeStart.y; + const sa = ax * by - bx * ay; - var ax = edgeEnd.x - edgeStart.x; - var ay = edgeEnd.y - edgeStart.y; - var bx = p.x - edgeStart.x; - var by = p.y - edgeStart.y; - var sa = ax * by - bx * ay; + if ( ( p.x === edgeStart.x ) && ( p.y === edgeStart.y ) ) { - if ( ( p.x === edgeStart.x ) && ( p.y === edgeStart.y ) ) { + classifyResult.loc = IntersectionLocationType.ORIGIN; + classifyResult.t = 0; + return; - classifyResult.loc = IntersectionLocationType.ORIGIN; - classifyResult.t = 0; - return; + } - } + if ( ( p.x === edgeEnd.x ) && ( p.y === edgeEnd.y ) ) { - if ( ( p.x === edgeEnd.x ) && ( p.y === edgeEnd.y ) ) { + classifyResult.loc = IntersectionLocationType.DESTINATION; + classifyResult.t = 1; + return; - classifyResult.loc = IntersectionLocationType.DESTINATION; - classifyResult.t = 1; - return; + } - } + if ( sa < - Number.EPSILON ) { - if ( sa < - Number.EPSILON ) { + classifyResult.loc = IntersectionLocationType.LEFT; + return; - classifyResult.loc = IntersectionLocationType.LEFT; - return; + } - } + if ( sa > Number.EPSILON ) { - if ( sa > Number.EPSILON ) { + classifyResult.loc = IntersectionLocationType.RIGHT; + return; - classifyResult.loc = IntersectionLocationType.RIGHT; - return; + } - } + if ( ( ( ax * bx ) < 0 ) || ( ( ay * by ) < 0 ) ) { - if ( ( ( ax * bx ) < 0 ) || ( ( ay * by ) < 0 ) ) { + classifyResult.loc = IntersectionLocationType.BEHIND; + return; - classifyResult.loc = IntersectionLocationType.BEHIND; - return; + } - } + if ( ( Math.sqrt( ax * ax + ay * ay ) ) < ( Math.sqrt( bx * bx + by * by ) ) ) { - if ( ( Math.sqrt( ax * ax + ay * ay ) ) < ( Math.sqrt( bx * bx + by * by ) ) ) { + classifyResult.loc = IntersectionLocationType.BEYOND; + return; - classifyResult.loc = IntersectionLocationType.BEYOND; - return; + } - } + let t; - var t; + if ( ax !== 0 ) { - if ( ax !== 0 ) { + t = bx / ax; - t = bx / ax; + } else { - } else { + t = by / ay; - t = by / ay; + } - } + classifyResult.loc = IntersectionLocationType.BETWEEN; + classifyResult.t = t; - classifyResult.loc = IntersectionLocationType.BETWEEN; - classifyResult.t = t; + } - } + function getIntersections( path1, path2 ) { - function getIntersections( path1, path2 ) { + const intersectionsRaw = []; + const intersections = []; - const intersectionsRaw = []; - const intersections = []; + for ( let index = 1; index < path1.length; index ++ ) { - for ( let index = 1; index < path1.length; index ++ ) { + const path1EdgeStart = path1[ index - 1 ]; + const path1EdgeEnd = path1[ index ]; - const path1EdgeStart = path1[ index - 1 ]; - const path1EdgeEnd = path1[ index ]; + for ( let index2 = 1; index2 < path2.length; index2 ++ ) { - for ( let index2 = 1; index2 < path2.length; index2 ++ ) { + const path2EdgeStart = path2[ index2 - 1 ]; + const path2EdgeEnd = path2[ index2 ]; - const path2EdgeStart = path2[ index2 - 1 ]; - const path2EdgeEnd = path2[ index2 ]; + const intersection = findEdgeIntersection( path1EdgeStart, path1EdgeEnd, path2EdgeStart, path2EdgeEnd ); - const intersection = findEdgeIntersection( path1EdgeStart, path1EdgeEnd, path2EdgeStart, path2EdgeEnd ); + if ( intersection !== null && intersectionsRaw.find( i => i.t <= intersection.t + Number.EPSILON && i.t >= intersection.t - Number.EPSILON ) === undefined ) { - if ( intersection !== null && intersectionsRaw.find( i => i.t <= intersection.t + Number.EPSILON && i.t >= intersection.t - Number.EPSILON ) === undefined ) { + intersectionsRaw.push( intersection ); + intersections.push( new Vector2( intersection.x, intersection.y ) ); - intersectionsRaw.push( intersection ); - intersections.push( new Vector2( intersection.x, intersection.y ) ); + } } } + return intersections; + } - return intersections; + function getScanlineIntersections( scanline, boundingBox, paths ) { - } + const center = new Vector2(); + boundingBox.getCenter( center ); - function getScanlineIntersections( scanline, boundingBox, paths ) { + const allIntersections = []; - const center = new Vector2(); - boundingBox.getCenter( center ); + paths.forEach( path => { - const allIntersections = []; + // check if the center of the bounding box is in the bounding box of the paths. + // this is a pruning method to limit the search of intersections in paths that can't envelop of the current path. + // if a path envelops another path. The center of that oter path, has to be inside the bounding box of the enveloping path. + if ( path.boundingBox.containsPoint( center ) ) { - paths.forEach( path => { + const intersections = getIntersections( scanline, path.points ); - // check if the center of the bounding box is in the bounding box of the paths. - // this is a pruning method to limit the search of intersections in paths that can't envelop of the current path. - // if a path envelops another path. The center of that oter path, has to be inside the bounding box of the enveloping path. - if ( path.boundingBox.containsPoint( center ) ) { + intersections.forEach( p => { - const intersections = getIntersections( scanline, path.points ); + allIntersections.push( { identifier: path.identifier, isCW: path.isCW, point: p } ); - intersections.forEach( p => { + } ); - allIntersections.push( { identifier: path.identifier, isCW: path.isCW, point: p } ); + } - } ); + } ); - } + allIntersections.sort( ( i1, i2 ) => { - } ); + return i1.point.x - i2.point.x; - allIntersections.sort( ( i1, i2 ) => { + } ); - return i1.point.x - i2.point.x; + return allIntersections; - } ); + } - return allIntersections; + function isHoleTo( simplePath, allPaths, scanlineMinX, scanlineMaxX, _fillRule ) { - } + if ( _fillRule === null || _fillRule === undefined || _fillRule === '' ) { - function isHoleTo( simplePath, allPaths, scanlineMinX, scanlineMaxX, _fillRule ) { + _fillRule = 'nonzero'; - if ( _fillRule === null || _fillRule === undefined || _fillRule === '' ) { + } - _fillRule = 'nonzero'; + const centerBoundingBox = new Vector2(); + simplePath.boundingBox.getCenter( centerBoundingBox ); - } + const scanline = [ new Vector2( scanlineMinX, centerBoundingBox.y ), new Vector2( scanlineMaxX, centerBoundingBox.y ) ]; - const centerBoundingBox = new Vector2(); - simplePath.boundingBox.getCenter( centerBoundingBox ); + const scanlineIntersections = getScanlineIntersections( scanline, simplePath.boundingBox, allPaths ); - const scanline = [ new Vector2( scanlineMinX, centerBoundingBox.y ), new Vector2( scanlineMaxX, centerBoundingBox.y ) ]; + scanlineIntersections.sort( ( i1, i2 ) => { - const scanlineIntersections = getScanlineIntersections( scanline, simplePath.boundingBox, allPaths ); + return i1.point.x - i2.point.x; - scanlineIntersections.sort( ( i1, i2 ) => { + } ); - return i1.point.x - i2.point.x; + const baseIntersections = []; + const otherIntersections = []; - } ); + scanlineIntersections.forEach( i => { - const baseIntersections = []; - const otherIntersections = []; + if ( i.identifier === simplePath.identifier ) { - scanlineIntersections.forEach( i => { + baseIntersections.push( i ); - if ( i.identifier === simplePath.identifier ) { + } else { - baseIntersections.push( i ); + otherIntersections.push( i ); - } else { + } - otherIntersections.push( i ); + } ); - } + const firstXOfPath = baseIntersections[ 0 ].point.x; - } ); + // build up the path hierarchy + const stack = []; + let i = 0; - const firstXOfPath = baseIntersections[ 0 ].point.x; + while ( i < otherIntersections.length && otherIntersections[ i ].point.x < firstXOfPath ) { - // build up the path hierarchy - const stack = []; - let i = 0; + if ( stack.length > 0 && stack[ stack.length - 1 ] === otherIntersections[ i ].identifier ) { - while ( i < otherIntersections.length && otherIntersections[ i ].point.x < firstXOfPath ) { + stack.pop(); - if ( stack.length > 0 && stack[ stack.length - 1 ] === otherIntersections[ i ].identifier ) { + } else { - stack.pop(); + stack.push( otherIntersections[ i ].identifier ); - } else { + } - stack.push( otherIntersections[ i ].identifier ); + i ++; } - i ++; - - } + stack.push( simplePath.identifier ); - stack.push( simplePath.identifier ); + if ( _fillRule === 'evenodd' ) { - if ( _fillRule === 'evenodd' ) { + const isHole = stack.length % 2 === 0 ? true : false; + const isHoleFor = stack[ stack.length - 2 ]; - const isHole = stack.length % 2 === 0 ? true : false; - const isHoleFor = stack[ stack.length - 2 ]; + return { identifier: simplePath.identifier, isHole: isHole, for: isHoleFor }; - return { identifier: simplePath.identifier, isHole: isHole, for: isHoleFor }; + } else if ( _fillRule === 'nonzero' ) { - } else if ( _fillRule === 'nonzero' ) { + // check if path is a hole by counting the amount of paths with alternating rotations it has to cross. + let isHole = true; + let isHoleFor = null; + let lastCWValue = null; - // check if path is a hole by counting the amount of paths with alternating rotations it has to cross. - let isHole = true; - let isHoleFor = null; - let lastCWValue = null; + for ( let i = 0; i < stack.length; i ++ ) { - for ( let i = 0; i < stack.length; i ++ ) { + const identifier = stack[ i ]; + if ( isHole ) { - const identifier = stack[ i ]; - if ( isHole ) { + lastCWValue = allPaths[ identifier ].isCW; + isHole = false; + isHoleFor = identifier; - lastCWValue = allPaths[ identifier ].isCW; - isHole = false; - isHoleFor = identifier; + } else if ( lastCWValue !== allPaths[ identifier ].isCW ) { - } else if ( lastCWValue !== allPaths[ identifier ].isCW ) { + lastCWValue = allPaths[ identifier ].isCW; + isHole = true; - lastCWValue = allPaths[ identifier ].isCW; - isHole = true; + } } - } + return { identifier: simplePath.identifier, isHole: isHole, for: isHoleFor }; - return { identifier: simplePath.identifier, isHole: isHole, for: isHoleFor }; + } else { - } else { + console.warn( 'fill-rule: "' + _fillRule + '" is currently not implemented.' ); - console.warn( 'fill-rule: "' + _fillRule + '" is currently not implemented.' ); + } } - } + // check for self intersecting paths + // TODO - // check for self intersecting paths - // TODO + // check intersecting paths + // TODO - // check intersecting paths - // TODO + // prepare paths for hole detection + let identifier = 0; - // prepare paths for hole detection - let identifier = 0; + let scanlineMinX = BIGNUMBER; + let scanlineMaxX = - BIGNUMBER; - let scanlineMinX = BIGNUMBER; - let scanlineMaxX = - BIGNUMBER; + let simplePaths = shapePath.subPaths.map( p => { - let simplePaths = shapePath.subPaths.map( p => { + const points = p.getPoints(); + let maxY = - BIGNUMBER; + let minY = BIGNUMBER; + let maxX = - BIGNUMBER; + let minX = BIGNUMBER; - const points = p.getPoints(); - let maxY = - BIGNUMBER; - let minY = BIGNUMBER; - let maxX = - BIGNUMBER; - let minX = BIGNUMBER; + //points.forEach(p => p.y *= -1); - //points.forEach(p => p.y *= -1); + for ( let i = 0; i < points.length; i ++ ) { - for ( let i = 0; i < points.length; i ++ ) { + const p = points[ i ]; - const p = points[ i ]; + if ( p.y > maxY ) { - if ( p.y > maxY ) { + maxY = p.y; - maxY = p.y; + } - } + if ( p.y < minY ) { - if ( p.y < minY ) { + minY = p.y; - minY = p.y; + } - } + if ( p.x > maxX ) { - if ( p.x > maxX ) { + maxX = p.x; - maxX = p.x; + } - } + if ( p.x < minX ) { - if ( p.x < minX ) { + minX = p.x; - minX = p.x; + } } - } - - // - if ( scanlineMaxX <= maxX ) { + // + if ( scanlineMaxX <= maxX ) { - scanlineMaxX = maxX + 1; + scanlineMaxX = maxX + 1; - } + } - if ( scanlineMinX >= minX ) { + if ( scanlineMinX >= minX ) { - scanlineMinX = minX - 1; + scanlineMinX = minX - 1; - } + } - return { points: points, isCW: ShapeUtils.isClockWise( points ), identifier: identifier ++, boundingBox: new Box2( new Vector2( minX, minY ), new Vector2( maxX, maxY ) ) }; + return { points: points, isCW: ShapeUtils.isClockWise( points ), identifier: identifier ++, boundingBox: new Box2( new Vector2( minX, minY ), new Vector2( maxX, maxY ) ) }; - } ); + } ); - simplePaths = simplePaths.filter( sp => sp.points.length > 0 ); + simplePaths = simplePaths.filter( sp => sp.points.length > 0 ); - // check if path is solid or a hole - const isAHole = simplePaths.map( p => isHoleTo( p, simplePaths, scanlineMinX, scanlineMaxX, shapePath.userData.style.fillRule ) ); + // check if path is solid or a hole + const isAHole = simplePaths.map( p => isHoleTo( p, simplePaths, scanlineMinX, scanlineMaxX, shapePath.userData.style.fillRule ) ); - const shapesToReturn = []; - simplePaths.forEach( p => { + const shapesToReturn = []; + simplePaths.forEach( p => { - const amIAHole = isAHole[ p.identifier ]; + const amIAHole = isAHole[ p.identifier ]; - if ( ! amIAHole.isHole ) { + if ( ! amIAHole.isHole ) { - const shape = new Shape( p.points ); - const holes = isAHole.filter( h => h.isHole && h.for === p.identifier ); - holes.forEach( h => { + const shape = new Shape( p.points ); + const holes = isAHole.filter( h => h.isHole && h.for === p.identifier ); + holes.forEach( h => { - const path = simplePaths[ h.identifier ]; - shape.holes.push( new Path( path.points ) ); + const path = simplePaths[ h.identifier ]; + shape.holes.push( new Path( path.points ) ); - } ); - shapesToReturn.push( shape ); + } ); + shapesToReturn.push( shape ); - } + } - } ); + } ); - return shapesToReturn; + return shapesToReturn; -}; + } -SVGLoader.getStrokeStyle = function ( width, color, lineJoin, lineCap, miterLimit ) { + static getStrokeStyle( width, color, lineJoin, lineCap, miterLimit ) { + + // Param width: Stroke width + // Param color: As returned by THREE.Color.getStyle() + // Param lineJoin: One of "round", "bevel", "miter" or "miter-limit" + // Param lineCap: One of "round", "square" or "butt" + // Param miterLimit: Maximum join length, in multiples of the "width" parameter (join is truncated if it exceeds that distance) + // Returns style object + + width = width !== undefined ? width : 1; + color = color !== undefined ? color : '#000'; + lineJoin = lineJoin !== undefined ? lineJoin : 'miter'; + lineCap = lineCap !== undefined ? lineCap : 'butt'; + miterLimit = miterLimit !== undefined ? miterLimit : 4; + + return { + strokeColor: color, + strokeWidth: width, + strokeLineJoin: lineJoin, + strokeLineCap: lineCap, + strokeMiterLimit: miterLimit + }; - // Param width: Stroke width - // Param color: As returned by THREE.Color.getStyle() - // Param lineJoin: One of "round", "bevel", "miter" or "miter-limit" - // Param lineCap: One of "round", "square" or "butt" - // Param miterLimit: Maximum join length, in multiples of the "width" parameter (join is truncated if it exceeds that distance) - // Returns style object + } - width = width !== undefined ? width : 1; - color = color !== undefined ? color : '#000'; - lineJoin = lineJoin !== undefined ? lineJoin : 'miter'; - lineCap = lineCap !== undefined ? lineCap : 'butt'; - miterLimit = miterLimit !== undefined ? miterLimit : 4; + static pointsToStroke( points, style, arcDivisions, minDistance ) { - return { - strokeColor: color, - strokeWidth: width, - strokeLineJoin: lineJoin, - strokeLineCap: lineCap, - strokeMiterLimit: miterLimit - }; + // Generates a stroke with some witdh around the given path. + // The path can be open or closed (last point equals to first point) + // Param points: Array of Vector2D (the path). Minimum 2 points. + // Param style: Object with SVG properties as returned by SVGLoader.getStrokeStyle(), or SVGLoader.parse() in the path.userData.style object + // Params arcDivisions: Arc divisions for round joins and endcaps. (Optional) + // Param minDistance: Points closer to this distance will be merged. (Optional) + // Returns BufferGeometry with stroke triangles (In plane z = 0). UV coordinates are generated ('u' along path. 'v' across it, from left to right) -}; + const vertices = []; + const normals = []; + const uvs = []; -SVGLoader.pointsToStroke = function ( points, style, arcDivisions, minDistance ) { + if ( SVGLoader.pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs ) === 0 ) { - // Generates a stroke with some witdh around the given path. - // The path can be open or closed (last point equals to first point) - // Param points: Array of Vector2D (the path). Minimum 2 points. - // Param style: Object with SVG properties as returned by SVGLoader.getStrokeStyle(), or SVGLoader.parse() in the path.userData.style object - // Params arcDivisions: Arc divisions for round joins and endcaps. (Optional) - // Param minDistance: Points closer to this distance will be merged. (Optional) - // Returns BufferGeometry with stroke triangles (In plane z = 0). UV coordinates are generated ('u' along path. 'v' across it, from left to right) + return null; - var vertices = []; - var normals = []; - var uvs = []; + } - if ( SVGLoader.pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs ) === 0 ) { + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + geometry.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - return null; + return geometry; } - var geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - geometry.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - return geometry; - -}; - -SVGLoader.pointsToStrokeWithBuffers = function () { - - var tempV2_1 = new Vector2(); - var tempV2_2 = new Vector2(); - var tempV2_3 = new Vector2(); - var tempV2_4 = new Vector2(); - var tempV2_5 = new Vector2(); - var tempV2_6 = new Vector2(); - var tempV2_7 = new Vector2(); - var lastPointL = new Vector2(); - var lastPointR = new Vector2(); - var point0L = new Vector2(); - var point0R = new Vector2(); - var currentPointL = new Vector2(); - var currentPointR = new Vector2(); - var nextPointL = new Vector2(); - var nextPointR = new Vector2(); - var innerPoint = new Vector2(); - var outerPoint = new Vector2(); - - return function ( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { + static pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { // This function can be called to update existing arrays or buffers. // Accepts same parameters as pointsToStroke, plus the buffers and optional offset. @@ -2160,6 +2141,24 @@ SVGLoader.pointsToStrokeWithBuffers = function () { // if 'vertices' parameter is undefined no triangles will be generated, but the returned vertices count will still be valid (useful to preallocate the buffers) // 'normals' and 'uvs' buffers are optional + const tempV2_1 = new Vector2(); + const tempV2_2 = new Vector2(); + const tempV2_3 = new Vector2(); + const tempV2_4 = new Vector2(); + const tempV2_5 = new Vector2(); + const tempV2_6 = new Vector2(); + const tempV2_7 = new Vector2(); + const lastPointL = new Vector2(); + const lastPointR = new Vector2(); + const point0L = new Vector2(); + const point0R = new Vector2(); + const currentPointL = new Vector2(); + const currentPointR = new Vector2(); + const nextPointL = new Vector2(); + const nextPointR = new Vector2(); + const innerPoint = new Vector2(); + const outerPoint = new Vector2(); + arcDivisions = arcDivisions !== undefined ? arcDivisions : 12; minDistance = minDistance !== undefined ? minDistance : 0.001; vertexOffset = vertexOffset !== undefined ? vertexOffset : 0; @@ -2167,29 +2166,29 @@ SVGLoader.pointsToStrokeWithBuffers = function () { // First ensure there are no duplicated points points = removeDuplicatedPoints( points ); - var numPoints = points.length; + const numPoints = points.length; if ( numPoints < 2 ) return 0; - var isClosed = points[ 0 ].equals( points[ numPoints - 1 ] ); + const isClosed = points[ 0 ].equals( points[ numPoints - 1 ] ); - var currentPoint; - var previousPoint = points[ 0 ]; - var nextPoint; + let currentPoint; + let previousPoint = points[ 0 ]; + let nextPoint; - var strokeWidth2 = style.strokeWidth / 2; + const strokeWidth2 = style.strokeWidth / 2; - var deltaU = 1 / ( numPoints - 1 ); - var u0 = 0; + const deltaU = 1 / ( numPoints - 1 ); + let u0 = 0, u1; - var innerSideModified; - var joinIsOnLeftSide; - var isMiter; - var initialJoinIsOnLeftSide = false; + let innerSideModified; + let joinIsOnLeftSide; + let isMiter; + let initialJoinIsOnLeftSide = false; - var numVertices = 0; - var currentCoordinate = vertexOffset * 3; - var currentCoordinateUV = vertexOffset * 2; + let numVertices = 0; + let currentCoordinate = vertexOffset * 3; + let currentCoordinateUV = vertexOffset * 2; // Get initial left and right stroke points getNormal( points[ 0 ], points[ 1 ], tempV2_1 ).multiplyScalar( strokeWidth2 ); @@ -2198,7 +2197,7 @@ SVGLoader.pointsToStrokeWithBuffers = function () { point0L.copy( lastPointL ); point0R.copy( lastPointR ); - for ( var iPoint = 1; iPoint < numPoints; iPoint ++ ) { + for ( let iPoint = 1; iPoint < numPoints; iPoint ++ ) { currentPoint = points[ iPoint ]; @@ -2219,14 +2218,14 @@ SVGLoader.pointsToStrokeWithBuffers = function () { } // Normal of previous segment in tempV2_1 - var normal1 = tempV2_1; + const normal1 = tempV2_1; getNormal( previousPoint, currentPoint, normal1 ); tempV2_3.copy( normal1 ).multiplyScalar( strokeWidth2 ); currentPointL.copy( currentPoint ).sub( tempV2_3 ); currentPointR.copy( currentPoint ).add( tempV2_3 ); - var u1 = u0 + deltaU; + u1 = u0 + deltaU; innerSideModified = false; @@ -2251,22 +2250,22 @@ SVGLoader.pointsToStrokeWithBuffers = function () { tempV2_3.subVectors( nextPoint, currentPoint ); tempV2_3.normalize(); - var dot = Math.abs( normal1.dot( tempV2_3 ) ); + const dot = Math.abs( normal1.dot( tempV2_3 ) ); // If path is straight, don't create join if ( dot !== 0 ) { // Compute inner and outer segment intersections - var miterSide = strokeWidth2 / dot; + const miterSide = strokeWidth2 / dot; tempV2_3.multiplyScalar( - miterSide ); tempV2_4.subVectors( currentPoint, previousPoint ); tempV2_5.copy( tempV2_4 ).setLength( miterSide ).add( tempV2_3 ); innerPoint.copy( tempV2_5 ).negate(); - var miterLength2 = tempV2_5.length(); - var segmentLengthPrev = tempV2_4.length(); + const miterLength2 = tempV2_5.length(); + const segmentLengthPrev = tempV2_4.length(); tempV2_4.divideScalar( segmentLengthPrev ); tempV2_6.subVectors( nextPoint, currentPoint ); - var segmentLengthNext = tempV2_6.length(); + const segmentLengthNext = tempV2_6.length(); tempV2_6.divideScalar( segmentLengthNext ); // Check that previous and next segments doesn't overlap with the innerPoint of intersection if ( tempV2_4.dot( innerPoint ) < segmentLengthPrev && tempV2_6.dot( innerPoint ) < segmentLengthNext ) { @@ -2334,7 +2333,7 @@ SVGLoader.pointsToStrokeWithBuffers = function () { case 'miter-clip': default: - var miterFraction = ( strokeWidth2 * style.strokeMiterLimit ) / miterLength2; + const miterFraction = ( strokeWidth2 * style.strokeMiterLimit ) / miterLength2; if ( miterFraction < 1 ) { @@ -2512,8 +2511,8 @@ SVGLoader.pointsToStrokeWithBuffers = function () { // Modify path first segment vertices to adjust to the segments inner and outer intersections - var lastOuter = outerPoint; - var lastInner = innerPoint; + let lastOuter = outerPoint; + let lastInner = innerPoint; if ( initialJoinIsOnLeftSide !== joinIsOnLeftSide ) { @@ -2610,15 +2609,15 @@ SVGLoader.pointsToStrokeWithBuffers = function () { tempV2_1.copy( p1 ).sub( center ).normalize(); tempV2_2.copy( p2 ).sub( center ).normalize(); - var angle = Math.PI; - var dot = tempV2_1.dot( tempV2_2 ); + let angle = Math.PI; + const dot = tempV2_1.dot( tempV2_2 ); if ( Math.abs( dot ) < 1 ) angle = Math.abs( Math.acos( dot ) ); angle /= arcDivisions; tempV2_3.copy( p1 ); - for ( var i = 0, il = arcDivisions - 1; i < il; i ++ ) { + for ( let i = 0, il = arcDivisions - 1; i < il; i ++ ) { tempV2_4.copy( tempV2_3 ).rotateAround( center, angle ); @@ -2814,7 +2813,7 @@ SVGLoader.pointsToStrokeWithBuffers = function () { tempV2_3.addVectors( tempV2_1, tempV2_2 ).add( center ); tempV2_4.subVectors( tempV2_2, tempV2_1 ).add( center ); - var vl = vertices.length; + const vl = vertices.length; // Modify already existing vertices if ( joinIsOnLeftSide ) { @@ -2850,8 +2849,8 @@ SVGLoader.pointsToStrokeWithBuffers = function () { // Creates a new array if necessary with duplicated points removed. // This does not remove duplicated initial and ending points of a closed path. - var dupPoints = false; - for ( var i = 1, n = points.length - 1; i < n; i ++ ) { + let dupPoints = false; + for ( let i = 1, n = points.length - 1; i < n; i ++ ) { if ( points[ i ].distanceTo( points[ i + 1 ] ) < minDistance ) { @@ -2864,10 +2863,10 @@ SVGLoader.pointsToStrokeWithBuffers = function () { if ( ! dupPoints ) return points; - var newPoints = []; + const newPoints = []; newPoints.push( points[ 0 ] ); - for ( var i = 1, n = points.length - 1; i < n; i ++ ) { + for ( let i = 1, n = points.length - 1; i < n; i ++ ) { if ( points[ i ].distanceTo( points[ i + 1 ] ) >= minDistance ) { @@ -2883,8 +2882,9 @@ SVGLoader.pointsToStrokeWithBuffers = function () { } - }; + } + -}(); +} export { SVGLoader };