diff --git a/examples/js/exporters/ColladaExporter.js b/examples/js/exporters/ColladaExporter.js index 52ebf59fb7dd22..47921c07e2666a 100644 --- a/examples/js/exporters/ColladaExporter.js +++ b/examples/js/exporters/ColladaExporter.js @@ -4,21 +4,18 @@ * https://github.com/gkjohnson/collada-exporter-js * * Usage: - * var exporter = new ColladaExporter(); + * const exporter = new ColladaExporter(); * - * var data = exporter.parse(mesh); + * const data = exporter.parse(mesh); * * Format Definition: * https://www.khronos.org/collada/ */ - var ColladaExporter = function () {}; + class ColladaExporter { - ColladaExporter.prototype = { - constructor: ColladaExporter, - parse: function ( object, onDone, options ) { + parse( object, onDone, options = {} ) { - options = options || {}; options = Object.assign( { version: '1.4.1', author: null, @@ -31,7 +28,7 @@ } - var version = options.version; + const version = options.version; if ( version !== '1.4.1' && version !== '1.5.0' ) { @@ -43,13 +40,13 @@ function format( urdf ) { - var IS_END_TAG = /^<\//; - var IS_SELF_CLOSING = /(\?>$)|(\/>$)/; - var HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/; + const IS_END_TAG = /^<\//; + const IS_SELF_CLOSING = /(\?>$)|(\/>$)/; + const HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/; - var pad = ( ch, num ) => num > 0 ? ch + pad( ch, num - 1 ) : ''; + const pad = ( ch, num ) => num > 0 ? ch + pad( ch, num - 1 ) : ''; - var tagnum = 0; + let tagnum = 0; return urdf.match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g ).map( tag => { if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && IS_END_TAG.test( tag ) ) { @@ -58,7 +55,7 @@ } - var res = `${pad( ' ', tagnum )}${tag}`; + const res = `${pad( ' ', tagnum )}${tag}`; if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) { @@ -75,10 +72,10 @@ function base64ToBuffer( str ) { - var b = atob( str ); - var buf = new Uint8Array( b.length ); + const b = atob( str ); + const buf = new Uint8Array( b.length ); - for ( var i = 0, l = buf.length; i < l; i ++ ) { + for ( let i = 0, l = buf.length; i < l; i ++ ) { buf[ i ] = b.charCodeAt( i ); @@ -88,7 +85,7 @@ } - var canvas, ctx; + let canvas, ctx; function imageToData( image, ext ) { @@ -98,26 +95,26 @@ canvas.height = image.height; ctx.drawImage( image, 0, 0 ); // Get the base64 encoded data - var base64data = canvas.toDataURL( `image/${ext}`, 1 ).replace( /^data:image\/(png|jpg);base64,/, '' ); // Convert to a uint8 array + const base64data = canvas.toDataURL( `image/${ext}`, 1 ).replace( /^data:image\/(png|jpg);base64,/, '' ); // Convert to a uint8 array return base64ToBuffer( base64data ); } // gets the attribute array. Generate a new array if the attribute is interleaved - var getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ]; + const getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ]; function attrBufferToArray( attr ) { if ( attr.isInterleavedBufferAttribute ) { // use the typed array constructor to save on memory - var arr = new attr.array.constructor( attr.count * attr.itemSize ); - var size = attr.itemSize; + const arr = new attr.array.constructor( attr.count * attr.itemSize ); + const size = attr.itemSize; - for ( var i = 0, l = attr.count; i < l; i ++ ) { + for ( let i = 0, l = attr.count; i < l; i ++ ) { - for ( var j = 0; j < size; j ++ ) { + for ( let j = 0; j < size; j ++ ) { arr[ i * size + j ] = attr[ getFuncs[ j ] ]( i ); @@ -146,14 +143,14 @@ function getAttribute( attr, name, params, type ) { - var array = attrBufferToArray( attr ); - var res = `` + `` + array.join( ' ' ) + '' + '' + `` + params.map( n => `` ).join( '' ) + '' + '' + ''; + const array = attrBufferToArray( attr ); + const res = `` + `` + array.join( ' ' ) + '' + '' + `` + params.map( n => `` ).join( '' ) + '' + '' + ''; return res; } // Returns the string for a node's transform information - var transMat; + let transMat; function getTransform( o ) { @@ -171,12 +168,12 @@ function processGeometry( g ) { - var info = geometryInfo.get( g ); + let info = geometryInfo.get( g ); if ( ! info ) { // convert the geometry to bufferGeometry if it isn't already - var bufferGeometry = g; + const bufferGeometry = g; if ( bufferGeometry.isBufferGeometry !== true ) { @@ -184,18 +181,18 @@ } - var meshid = `Mesh${libraryGeometries.length + 1}`; - var indexCount = bufferGeometry.index ? bufferGeometry.index.count * bufferGeometry.index.itemSize : bufferGeometry.attributes.position.count; - var groups = bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? bufferGeometry.groups : [ { + const meshid = `Mesh${libraryGeometries.length + 1}`; + const indexCount = bufferGeometry.index ? bufferGeometry.index.count * bufferGeometry.index.itemSize : bufferGeometry.attributes.position.count; + const groups = bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? bufferGeometry.groups : [ { start: 0, count: indexCount, materialIndex: 0 } ]; - var gname = g.name ? ` name="${g.name}"` : ''; - var gnode = ``; // define the geometry node and the vertices for the geometry + const gname = g.name ? ` name="${g.name}"` : ''; + let gnode = ``; // define the geometry node and the vertices for the geometry - var posName = `${meshid}-position`; - var vertName = `${meshid}-vertices`; + const posName = `${meshid}-position`; + const vertName = `${meshid}-vertices`; gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' ); gnode += ``; // NOTE: We're not optimizing the attribute arrays here, so they're all the same length and // can therefore share the same triangle indices. However, MeshLab seems to have trouble opening @@ -203,11 +200,11 @@ // MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/ // serialize normals - var triangleInputs = ``; + let triangleInputs = ``; if ( 'normal' in bufferGeometry.attributes ) { - var normName = `${meshid}-normal`; + const normName = `${meshid}-normal`; gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' ); triangleInputs += ``; @@ -216,7 +213,7 @@ if ( 'uv' in bufferGeometry.attributes ) { - var uvName = `${meshid}-texcoord`; + const uvName = `${meshid}-texcoord`; gnode += getAttribute( bufferGeometry.attributes.uv, uvName, [ 'S', 'T' ], 'float' ); triangleInputs += ``; @@ -225,7 +222,7 @@ if ( 'uv2' in bufferGeometry.attributes ) { - var uvName = `${meshid}-texcoord2`; + const uvName = `${meshid}-texcoord2`; gnode += getAttribute( bufferGeometry.attributes.uv2, uvName, [ 'S', 'T' ], 'float' ); triangleInputs += ``; @@ -234,13 +231,13 @@ if ( 'color' in bufferGeometry.attributes ) { - var colName = `${meshid}-color`; + const colName = `${meshid}-color`; gnode += getAttribute( bufferGeometry.attributes.color, colName, [ 'X', 'Y', 'Z' ], 'uint8' ); triangleInputs += ``; } - var indexArray = null; + let indexArray = null; if ( bufferGeometry.index ) { @@ -250,15 +247,15 @@ indexArray = new Array( indexCount ); - for ( var i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i; + for ( let i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i; } - for ( var i = 0, l = groups.length; i < l; i ++ ) { + for ( let i = 0, l = groups.length; i < l; i ++ ) { - var group = groups[ i ]; - var subarr = subArray( indexArray, group.start, group.count ); - var polycount = subarr.length / 3; + const group = groups[ i ]; + const subarr = subArray( indexArray, group.start, group.count ); + const polycount = subarr.length / 3; gnode += ``; gnode += triangleInputs; gnode += `

${subarr.join( ' ' )}

`; @@ -284,14 +281,14 @@ function processTexture( tex ) { - var texid = imageMap.get( tex ); + let texid = imageMap.get( tex ); if ( texid == null ) { texid = `image-${libraryImages.length + 1}`; - var ext = 'png'; - var name = tex.name || texid; - var imageNode = ``; + const ext = 'png'; + const name = tex.name || texid; + let imageNode = ``; if ( version === '1.5.0' ) { @@ -325,12 +322,12 @@ function processMaterial( m ) { - var matid = materialMap.get( m ); + let matid = materialMap.get( m ); if ( matid == null ) { matid = `Mat${libraryEffects.length + 1}`; - var type = 'phong'; + let type = 'phong'; if ( m.isMeshLambertMaterial === true ) { @@ -351,15 +348,15 @@ } - var emissive = m.emissive ? m.emissive : new THREE.Color( 0, 0, 0 ); - var diffuse = m.color ? m.color : new THREE.Color( 0, 0, 0 ); - var specular = m.specular ? m.specular : new THREE.Color( 1, 1, 1 ); - var shininess = m.shininess || 0; - var reflectivity = m.reflectivity || 0; // Do not export and alpha map for the reasons mentioned in issue (#13792) + const emissive = m.emissive ? m.emissive : new THREE.Color( 0, 0, 0 ); + const diffuse = m.color ? m.color : new THREE.Color( 0, 0, 0 ); + const specular = m.specular ? m.specular : new THREE.Color( 1, 1, 1 ); + const shininess = m.shininess || 0; + const reflectivity = m.reflectivity || 0; // Do not export and alpha map for the reasons mentioned in issue (#13792) // in three.js alpha maps are black and white, but collada expects the alpha // channel to specify the transparency - var transparencyNode = ''; + let transparencyNode = ''; if ( m.transparent === true ) { @@ -373,10 +370,10 @@ } - var techniqueNode = `<${type}>` + '' + ( m.emissiveMap ? '' : `${emissive.r} ${emissive.g} ${emissive.b} 1` ) + '' + ( type !== 'constant' ? '' + ( m.map ? '' : `${diffuse.r} ${diffuse.g} ${diffuse.b} 1` ) + '' : '' ) + ( type !== 'constant' ? '' + ( m.normalMap ? '' : '' ) + '' : '' ) + ( type === 'phong' ? `${specular.r} ${specular.g} ${specular.b} 1` + '' + ( m.specularMap ? '' : `${shininess}` ) + '' : '' ) + `${diffuse.r} ${diffuse.g} ${diffuse.b} 1` + `${reflectivity}` + transparencyNode + ``; - var effectnode = `` + '' + ( m.map ? '' + `${processTexture( m.map )}` + '' + 'diffuse-surface' : '' ) + ( m.specularMap ? '' + `${processTexture( m.specularMap )}` + '' + 'specular-surface' : '' ) + ( m.emissiveMap ? '' + `${processTexture( m.emissiveMap )}` + '' + 'emissive-surface' : '' ) + ( m.normalMap ? '' + `${processTexture( m.normalMap )}` + '' + 'bump-surface' : '' ) + techniqueNode + ( m.side === THREE.DoubleSide ? '1' : '' ) + '' + ''; - var materialName = m.name ? ` name="${m.name}"` : ''; - var materialNode = ``; + const techniqueNode = `<${type}>` + '' + ( m.emissiveMap ? '' : `${emissive.r} ${emissive.g} ${emissive.b} 1` ) + '' + ( type !== 'constant' ? '' + ( m.map ? '' : `${diffuse.r} ${diffuse.g} ${diffuse.b} 1` ) + '' : '' ) + ( type !== 'constant' ? '' + ( m.normalMap ? '' : '' ) + '' : '' ) + ( type === 'phong' ? `${specular.r} ${specular.g} ${specular.b} 1` + '' + ( m.specularMap ? '' : `${shininess}` ) + '' : '' ) + `${diffuse.r} ${diffuse.g} ${diffuse.b} 1` + `${reflectivity}` + transparencyNode + ``; + const effectnode = `` + '' + ( m.map ? '' + `${processTexture( m.map )}` + '' + 'diffuse-surface' : '' ) + ( m.specularMap ? '' + `${processTexture( m.specularMap )}` + '' + 'specular-surface' : '' ) + ( m.emissiveMap ? '' + `${processTexture( m.emissiveMap )}` + '' + 'emissive-surface' : '' ) + ( m.normalMap ? '' + `${processTexture( m.normalMap )}` + '' + 'bump-surface' : '' ) + techniqueNode + ( m.side === THREE.DoubleSide ? '1' : '' ) + '' + ''; + const materialName = m.name ? ` name="${m.name}"` : ''; + const materialNode = ``; libraryMaterials.push( materialNode ); libraryEffects.push( effectnode ); materialMap.set( m, matid ); @@ -390,24 +387,24 @@ function processObject( o ) { - var node = ``; + let node = ``; node += getTransform( o ); if ( o.isMesh === true && o.geometry !== null ) { // function returns the id associated with the mesh and a "BufferGeometry" version // of the geometry in case it's not a geometry. - var geomInfo = processGeometry( o.geometry ); - var meshid = geomInfo.meshid; - var geometry = geomInfo.bufferGeometry; // ids of the materials to bind to the geometry + const geomInfo = processGeometry( o.geometry ); + const meshid = geomInfo.meshid; + const geometry = geomInfo.bufferGeometry; // ids of the materials to bind to the geometry - var matids = null; - var matidsArray = []; // get a list of materials to bind to the sub groups of the geometry. + let matids = null; + let matidsArray; // get a list of materials to bind to the sub groups of the geometry. // If the amount of subgroups is greater than the materials, than reuse // the materials. - var mat = o.material || new THREE.MeshBasicMaterial(); - var materials = Array.isArray( mat ) ? mat : [ mat ]; + const mat = o.material || new THREE.MeshBasicMaterial(); + const materials = Array.isArray( mat ) ? mat : [ mat ]; if ( geometry.groups.length > materials.length ) { @@ -430,17 +427,17 @@ } - var geometryInfo = new WeakMap(); - var materialMap = new WeakMap(); - var imageMap = new WeakMap(); - var textures = []; - var libraryImages = []; - var libraryGeometries = []; - var libraryEffects = []; - var libraryMaterials = []; - var libraryVisualScenes = processObject( object ); - var specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/'; - var dae = '' + `` + '' + ( '' + 'three.js Collada Exporter' + ( options.author !== null ? `${options.author}` : '' ) + '' + `${new Date().toISOString()}` + `${new Date().toISOString()}` + 'Y_UP' ) + ''; + const geometryInfo = new WeakMap(); + const materialMap = new WeakMap(); + const imageMap = new WeakMap(); + const textures = []; + const libraryImages = []; + const libraryGeometries = []; + const libraryEffects = []; + const libraryMaterials = []; + const libraryVisualScenes = processObject( object ); + const specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/'; + let dae = '' + `` + '' + ( '' + 'three.js Collada Exporter' + ( options.author !== null ? `${options.author}` : '' ) + '' + `${new Date().toISOString()}` + `${new Date().toISOString()}` + 'Y_UP' ) + ''; dae += `${libraryImages.join( '' )}`; dae += `${libraryEffects.join( '' )}`; dae += `${libraryMaterials.join( '' )}`; @@ -448,7 +445,7 @@ dae += `${libraryVisualScenes}`; dae += ''; dae += ''; - var res = { + const res = { data: format( dae ), textures }; @@ -462,7 +459,8 @@ return res; } - }; + + } THREE.ColladaExporter = ColladaExporter; diff --git a/examples/js/exporters/DRACOExporter.js b/examples/js/exporters/DRACOExporter.js index 16690722d400a5..952f0498282312 100644 --- a/examples/js/exporters/DRACOExporter.js +++ b/examples/js/exporters/DRACOExporter.js @@ -15,11 +15,17 @@ */ /* global DracoEncoderModule */ - var DRACOExporter = function () {}; - - DRACOExporter.prototype = { - constructor: DRACOExporter, - parse: function ( object, options ) { + class DRACOExporter { + + parse( object, options = { + decodeSpeed: 5, + encodeSpeed: 5, + encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, + quantization: [ 16, 8, 8, 8, 8 ], + exportUvs: true, + exportNormals: true, + exportColor: false + } ) { if ( object.isBufferGeometry === true ) { @@ -33,25 +39,11 @@ } - if ( options === undefined ) { - - options = { - decodeSpeed: 5, - encodeSpeed: 5, - encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, - quantization: [ 16, 8, 8, 8, 8 ], - exportUvs: true, - exportNormals: true, - exportColor: false - }; - - } - - var geometry = object.geometry; - var dracoEncoder = DracoEncoderModule(); - var encoder = new dracoEncoder.Encoder(); - var builder; - var dracoObject; + const geometry = object.geometry; + const dracoEncoder = DracoEncoderModule(); + const encoder = new dracoEncoder.Encoder(); + let builder; + let dracoObject; if ( geometry.isBufferGeometry !== true ) { @@ -63,9 +55,9 @@ builder = new dracoEncoder.MeshBuilder(); dracoObject = new dracoEncoder.Mesh(); - var vertices = geometry.getAttribute( 'position' ); + const vertices = geometry.getAttribute( 'position' ); builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); - var faces = geometry.getIndex(); + const faces = geometry.getIndex(); if ( faces !== null ) { @@ -73,9 +65,9 @@ } else { - var faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); + const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); - for ( var i = 0; i < faces.length; i ++ ) { + for ( let i = 0; i < faces.length; i ++ ) { faces[ i ] = i; @@ -87,7 +79,7 @@ if ( options.exportNormals === true ) { - var normals = geometry.getAttribute( 'normal' ); + const normals = geometry.getAttribute( 'normal' ); if ( normals !== undefined ) { @@ -99,7 +91,7 @@ if ( options.exportUvs === true ) { - var uvs = geometry.getAttribute( 'uv' ); + const uvs = geometry.getAttribute( 'uv' ); if ( uvs !== undefined ) { @@ -111,7 +103,7 @@ if ( options.exportColor === true ) { - var colors = geometry.getAttribute( 'color' ); + const colors = geometry.getAttribute( 'color' ); if ( colors !== undefined ) { @@ -125,12 +117,12 @@ builder = new dracoEncoder.PointCloudBuilder(); dracoObject = new dracoEncoder.PointCloud(); - var vertices = geometry.getAttribute( 'position' ); + const vertices = geometry.getAttribute( 'position' ); builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); if ( options.exportColor === true ) { - var colors = geometry.getAttribute( 'color' ); + const colors = geometry.getAttribute( 'color' ); if ( colors !== undefined ) { @@ -147,10 +139,10 @@ } //Compress using draco encoder - var encodedData = new dracoEncoder.DracoInt8Array(); //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). + const encodedData = new dracoEncoder.DracoInt8Array(); //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). - var encodeSpeed = options.encodeSpeed !== undefined ? options.encodeSpeed : 5; - var decodeSpeed = options.decodeSpeed !== undefined ? options.decodeSpeed : 5; + const encodeSpeed = options.encodeSpeed !== undefined ? options.encodeSpeed : 5; + const decodeSpeed = options.decodeSpeed !== undefined ? options.decodeSpeed : 5; encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); // Sets the desired encoding method for a given geometry. if ( options.encoderMethod !== undefined ) { @@ -163,7 +155,7 @@ if ( options.quantization !== undefined ) { - for ( var i = 0; i < 5; i ++ ) { + for ( let i = 0; i < 5; i ++ ) { if ( options.quantization[ i ] !== undefined ) { @@ -175,7 +167,7 @@ } - var length; + let length; if ( object.isMesh === true ) { @@ -196,9 +188,9 @@ } //Copy encoded data to buffer. - var outputData = new Int8Array( new ArrayBuffer( length ) ); + const outputData = new Int8Array( new ArrayBuffer( length ) ); - for ( var i = 0; i < length; i ++ ) { + for ( let i = 0; i < length; i ++ ) { outputData[ i ] = encodedData.GetValue( i ); @@ -210,7 +202,9 @@ return outputData; } - }; // Encoder methods + + } // Encoder methods + DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; // Geometry type diff --git a/examples/js/exporters/GLTFExporter.js b/examples/js/exporters/GLTFExporter.js index e12bcdaab12868..d5c507cd038771 100644 --- a/examples/js/exporters/GLTFExporter.js +++ b/examples/js/exporters/GLTFExporter.js @@ -1,8 +1,8 @@ ( function () { - var GLTFExporter = function () { + class GLTFExporter { - function GLTFExporter() { + constructor() { this.pluginCallbacks = []; this.register( function ( writer ) { @@ -23,265 +23,268 @@ } - GLTFExporter.prototype = { - constructor: GLTFExporter, - register: function ( callback ) { + register( callback ) { - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - this.pluginCallbacks.push( callback ); + this.pluginCallbacks.push( callback ); - } - - return this; - - }, - unregister: function ( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - - } - - return this; - - }, + } - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input THREE.Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - parse: function ( input, onDone, options ) { + return this; - var writer = new GLTFWriter(); - var plugins = []; + } - for ( var i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { + unregister( callback ) { - plugins.push( this.pluginCallbacks[ i ]( writer ) ); + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - } - - writer.setPlugins( plugins ); - writer.write( input, onDone, options ); + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); } - }; //------------------------------------------------------------------------------ - // Constants - //------------------------------------------------------------------------------ - - var WEBGL_CONSTANTS = { - POINTS: 0x0000, - LINES: 0x0001, - LINE_LOOP: 0x0002, - LINE_STRIP: 0x0003, - TRIANGLES: 0x0004, - TRIANGLE_STRIP: 0x0005, - TRIANGLE_FAN: 0x0006, - UNSIGNED_BYTE: 0x1401, - UNSIGNED_SHORT: 0x1403, - FLOAT: 0x1406, - UNSIGNED_INT: 0x1405, - ARRAY_BUFFER: 0x8892, - ELEMENT_ARRAY_BUFFER: 0x8893, - NEAREST: 0x2600, - LINEAR: 0x2601, - NEAREST_MIPMAP_NEAREST: 0x2700, - LINEAR_MIPMAP_NEAREST: 0x2701, - NEAREST_MIPMAP_LINEAR: 0x2702, - LINEAR_MIPMAP_LINEAR: 0x2703, - CLAMP_TO_EDGE: 33071, - MIRRORED_REPEAT: 33648, - REPEAT: 10497 - }; - var THREE_TO_WEBGL = {}; - THREE_TO_WEBGL[ THREE.NearestFilter ] = WEBGL_CONSTANTS.NEAREST; - THREE_TO_WEBGL[ THREE.NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; - THREE_TO_WEBGL[ THREE.NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; - THREE_TO_WEBGL[ THREE.LinearFilter ] = WEBGL_CONSTANTS.LINEAR; - THREE_TO_WEBGL[ THREE.LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; - THREE_TO_WEBGL[ THREE.LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; - THREE_TO_WEBGL[ THREE.ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; - THREE_TO_WEBGL[ THREE.RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; - THREE_TO_WEBGL[ THREE.MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; - var PATH_PROPERTIES = { - scale: 'scale', - position: 'translation', - quaternion: 'rotation', - morphTargetInfluences: 'weights' - }; // GLB constants - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - - var GLB_HEADER_BYTES = 12; - var GLB_HEADER_MAGIC = 0x46546C67; - var GLB_VERSION = 2; - var GLB_CHUNK_PREFIX_BYTES = 8; - var GLB_CHUNK_TYPE_JSON = 0x4E4F534A; - var GLB_CHUNK_TYPE_BIN = 0x004E4942; //------------------------------------------------------------------------------ - // Utility functions - //------------------------------------------------------------------------------ - - /** - * Compare two arrays - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {Boolean} Returns true if both arrays are equal - */ - - function equalArray( array1, array2 ) { - - return array1.length === array2.length && array1.every( function ( element, index ) { - return element === array2[ index ]; - - } ); + return this; } /** - * Converts a string to an ArrayBuffer. - * @param {string} text - * @return {ArrayBuffer} + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input THREE.Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options */ - function stringToArrayBuffer( text ) { + parse( input, onDone, options ) { - if ( window.TextEncoder !== undefined ) { + const writer = new GLTFWriter(); + const plugins = []; - return new TextEncoder().encode( text ).buffer; + for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { + + plugins.push( this.pluginCallbacks[ i ]( writer ) ); } - var array = new Uint8Array( new ArrayBuffer( text.length ) ); + writer.setPlugins( plugins ); + writer.write( input, onDone, options ); - for ( var i = 0, il = text.length; i < il; i ++ ) { + } - var value = text.charCodeAt( i ); // Replacing multi-byte character with space(0x20). + } //------------------------------------------------------------------------------ + // Constants + //------------------------------------------------------------------------------ + + + const WEBGL_CONSTANTS = { + POINTS: 0x0000, + LINES: 0x0001, + LINE_LOOP: 0x0002, + LINE_STRIP: 0x0003, + TRIANGLES: 0x0004, + TRIANGLE_STRIP: 0x0005, + TRIANGLE_FAN: 0x0006, + UNSIGNED_BYTE: 0x1401, + UNSIGNED_SHORT: 0x1403, + FLOAT: 0x1406, + UNSIGNED_INT: 0x1405, + ARRAY_BUFFER: 0x8892, + ELEMENT_ARRAY_BUFFER: 0x8893, + NEAREST: 0x2600, + LINEAR: 0x2601, + NEAREST_MIPMAP_NEAREST: 0x2700, + LINEAR_MIPMAP_NEAREST: 0x2701, + NEAREST_MIPMAP_LINEAR: 0x2702, + LINEAR_MIPMAP_LINEAR: 0x2703, + CLAMP_TO_EDGE: 33071, + MIRRORED_REPEAT: 33648, + REPEAT: 10497 + }; + const THREE_TO_WEBGL = {}; + THREE_TO_WEBGL[ THREE.NearestFilter ] = WEBGL_CONSTANTS.NEAREST; + THREE_TO_WEBGL[ THREE.NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; + THREE_TO_WEBGL[ THREE.NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; + THREE_TO_WEBGL[ THREE.LinearFilter ] = WEBGL_CONSTANTS.LINEAR; + THREE_TO_WEBGL[ THREE.LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; + THREE_TO_WEBGL[ THREE.LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; + THREE_TO_WEBGL[ THREE.ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; + THREE_TO_WEBGL[ THREE.RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; + THREE_TO_WEBGL[ THREE.MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; + const PATH_PROPERTIES = { + scale: 'scale', + position: 'translation', + quaternion: 'rotation', + morphTargetInfluences: 'weights' + }; // GLB constants + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + + const GLB_HEADER_BYTES = 12; + const GLB_HEADER_MAGIC = 0x46546C67; + const GLB_VERSION = 2; + const GLB_CHUNK_PREFIX_BYTES = 8; + const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; + const GLB_CHUNK_TYPE_BIN = 0x004E4942; //------------------------------------------------------------------------------ + // Utility functions + //------------------------------------------------------------------------------ + + /** + * Compare two arrays + * @param {Array} array1 Array 1 to compare + * @param {Array} array2 Array 2 to compare + * @return {Boolean} Returns true if both arrays are equal + */ + + function equalArray( array1, array2 ) { + + return array1.length === array2.length && array1.every( function ( element, index ) { + + return element === array2[ index ]; + + } ); + + } + /** + * Converts a string to an ArrayBuffer. + * @param {string} text + * @return {ArrayBuffer} + */ + + + function stringToArrayBuffer( text ) { + + if ( window.TextEncoder !== undefined ) { + + return new TextEncoder().encode( text ).buffer; - array[ i ] = value > 0xFF ? 0x20 : value; + } - } + const array = new Uint8Array( new ArrayBuffer( text.length ) ); - return array.buffer; + for ( let i = 0, il = text.length; i < il; i ++ ) { + + const value = text.charCodeAt( i ); // Replacing multi-byte character with space(0x20). + + array[ i ] = value > 0xFF ? 0x20 : value; } - /** - * Is identity matrix - * - * @param {Matrix4} matrix - * @returns {Boolean} Returns true, if parameter is identity matrix - */ + return array.buffer; - function isIdentityMatrix( matrix ) { + } + /** + * Is identity matrix + * + * @param {Matrix4} matrix + * @returns {Boolean} Returns true, if parameter is identity matrix + */ - return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); - } - /** - * Get the min and max vectors from the given attribute - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {Integer} start - * @param {Integer} count - * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) - */ + function isIdentityMatrix( matrix ) { + return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); - function getMinMax( attribute, start, count ) { + } + /** + * Get the min and max vectors from the given attribute + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {Integer} start + * @param {Integer} count + * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) + */ - var output = { - min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), - max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) - }; - for ( var i = start; i < start + count; i ++ ) { + function getMinMax( attribute, start, count ) { - for ( var a = 0; a < attribute.itemSize; a ++ ) { + const output = { + min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), + max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) + }; - var value; + for ( let i = start; i < start + count; i ++ ) { - if ( attribute.itemSize > 4 ) { + for ( let a = 0; a < attribute.itemSize; a ++ ) { - // no support for interleaved data for itemSize > 4 - value = attribute.array[ i * attribute.itemSize + a ]; + let value; - } else { + if ( attribute.itemSize > 4 ) { - if ( a === 0 ) value = attribute.getX( i ); else if ( a === 1 ) value = attribute.getY( i ); else if ( a === 2 ) value = attribute.getZ( i ); else if ( a === 3 ) value = attribute.getW( i ); + // no support for interleaved data for itemSize > 4 + value = attribute.array[ i * attribute.itemSize + a ]; - } + } else { - output.min[ a ] = Math.min( output.min[ a ], value ); - output.max[ a ] = Math.max( output.max[ a ], value ); + if ( a === 0 ) value = attribute.getX( i ); else if ( a === 1 ) value = attribute.getY( i ); else if ( a === 2 ) value = attribute.getZ( i ); else if ( a === 3 ) value = attribute.getW( i ); } - } + output.min[ a ] = Math.min( output.min[ a ], value ); + output.max[ a ] = Math.max( output.max[ a ], value ); - return output; + } } - /** - * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - * - * @param {Integer} bufferSize The size the original buffer. - * @returns {Integer} new buffer size with required padding. - * - */ + return output; - function getPaddedBufferSize( bufferSize ) { + } + /** + * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + * + * @param {Integer} bufferSize The size the original buffer. + * @returns {Integer} new buffer size with required padding. + * + */ - return Math.ceil( bufferSize / 4 ) * 4; - } - /** - * Returns a buffer aligned to 4-byte boundary. - * - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {Integer} paddingByte (Optional) - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer - */ + function getPaddedBufferSize( bufferSize ) { + return Math.ceil( bufferSize / 4 ) * 4; - function getPaddedArrayBuffer( arrayBuffer, paddingByte ) { + } + /** + * Returns a buffer aligned to 4-byte boundary. + * + * @param {ArrayBuffer} arrayBuffer Buffer to pad + * @param {Integer} paddingByte (Optional) + * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + */ - paddingByte = paddingByte || 0; - var paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); - if ( paddedLength !== arrayBuffer.byteLength ) { + function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { - var array = new Uint8Array( paddedLength ); - array.set( new Uint8Array( arrayBuffer ) ); + const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); - if ( paddingByte !== 0 ) { + if ( paddedLength !== arrayBuffer.byteLength ) { - for ( var i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { + const array = new Uint8Array( paddedLength ); + array.set( new Uint8Array( arrayBuffer ) ); - array[ i ] = paddingByte; + if ( paddingByte !== 0 ) { - } + for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { - } + array[ i ] = paddingByte; - return array.buffer; + } } - return arrayBuffer; + return array.buffer; } - var cachedCanvas = null; - /** - * Writer - */ + return arrayBuffer; - function GLTFWriter() { + } + + let cachedCanvas = null; + /** + * Writer + */ + + class GLTFWriter { + + constructor() { this.plugins = []; this.options = {}; @@ -311,1885 +314,1911 @@ } - GLTFWriter.prototype = { - constructor: GLTFWriter, - setPlugins: function ( plugins ) { - - this.plugins = plugins; + setPlugins( plugins ) { - }, + this.plugins = plugins; - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input THREE.Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - write: function ( input, onDone, options ) { + } + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input THREE.Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options + */ - this.options = Object.assign( {}, { - // default options - binary: false, - trs: false, - onlyVisible: true, - truncateDrawRange: true, - embedImages: true, - maxTextureSize: Infinity, - animations: [], - includeCustomExtensions: false - }, options ); - if ( this.options.animations.length > 0 ) { + write( input, onDone, options ) { - // Only TRS properties, and not matrices, may be targeted by animation. - this.options.trs = true; + this.options = Object.assign( {}, { + // default options + binary: false, + trs: false, + onlyVisible: true, + truncateDrawRange: true, + embedImages: true, + maxTextureSize: Infinity, + animations: [], + includeCustomExtensions: false + }, options ); - } + if ( this.options.animations.length > 0 ) { - this.processInput( input ); - var writer = this; - Promise.all( this.pending ).then( function () { + // Only TRS properties, and not matrices, may be targeted by animation. + this.options.trs = true; - var buffers = writer.buffers; - var json = writer.json; - var options = writer.options; - var extensionsUsed = writer.extensionsUsed; // Merge buffers. - - var blob = new Blob( buffers, { - type: 'application/octet-stream' - } ); // Declare extensions. + } - var extensionsUsedList = Object.keys( extensionsUsed ); - if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; // Update bytelength of the single buffer. + this.processInput( input ); + const writer = this; + Promise.all( this.pending ).then( function () { - if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; + const buffers = writer.buffers; + const json = writer.json; + const options = writer.options; + const extensionsUsed = writer.extensionsUsed; // Merge buffers. - if ( options.binary === true ) { + const blob = new Blob( buffers, { + type: 'application/octet-stream' + } ); // Declare extensions. - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - var reader = new window.FileReader(); - reader.readAsArrayBuffer( blob ); + const extensionsUsedList = Object.keys( extensionsUsed ); + if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; // Update bytelength of the single buffer. - reader.onloadend = function () { + if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; - // Binary chunk. - var binaryChunk = getPaddedArrayBuffer( reader.result ); - var binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); - binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); // JSON chunk. - - var jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); - var jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); - jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); // GLB header. - - var header = new ArrayBuffer( GLB_HEADER_BYTES ); - var headerView = new DataView( header ); - headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); - headerView.setUint32( 4, GLB_VERSION, true ); - var totalByteLength = GLB_HEADER_BYTES + jsonChunkPrefix.byteLength + jsonChunk.byteLength + binaryChunkPrefix.byteLength + binaryChunk.byteLength; - headerView.setUint32( 8, totalByteLength, true ); - var glbBlob = new Blob( [ header, jsonChunkPrefix, jsonChunk, binaryChunkPrefix, binaryChunk ], { - type: 'application/octet-stream' - } ); - var glbReader = new window.FileReader(); - glbReader.readAsArrayBuffer( glbBlob ); + if ( options.binary === true ) { - glbReader.onloadend = function () { + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + const reader = new window.FileReader(); + reader.readAsArrayBuffer( blob ); - onDone( glbReader.result ); + reader.onloadend = function () { - }; + // Binary chunk. + const binaryChunk = getPaddedArrayBuffer( reader.result ); + const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); + binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); // JSON chunk. + + const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); + const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); + jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); // GLB header. + + const header = new ArrayBuffer( GLB_HEADER_BYTES ); + const headerView = new DataView( header ); + headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); + headerView.setUint32( 4, GLB_VERSION, true ); + const totalByteLength = GLB_HEADER_BYTES + jsonChunkPrefix.byteLength + jsonChunk.byteLength + binaryChunkPrefix.byteLength + binaryChunk.byteLength; + headerView.setUint32( 8, totalByteLength, true ); + const glbBlob = new Blob( [ header, jsonChunkPrefix, jsonChunk, binaryChunkPrefix, binaryChunk ], { + type: 'application/octet-stream' + } ); + const glbReader = new window.FileReader(); + glbReader.readAsArrayBuffer( glbBlob ); + + glbReader.onloadend = function () { + + onDone( glbReader.result ); }; - } else { + }; - if ( json.buffers && json.buffers.length > 0 ) { + } else { - var reader = new window.FileReader(); - reader.readAsDataURL( blob ); + if ( json.buffers && json.buffers.length > 0 ) { - reader.onloadend = function () { + const reader = new window.FileReader(); + reader.readAsDataURL( blob ); - var base64data = reader.result; - json.buffers[ 0 ].uri = base64data; - onDone( json ); + reader.onloadend = function () { - }; + const base64data = reader.result; + json.buffers[ 0 ].uri = base64data; + onDone( json ); - } else { + }; - onDone( json ); + } else { - } + onDone( json ); } - } ); + } - }, + } ); - /** - * Serializes a userData. - * - * @param {THREE.Object3D|THREE.Material} object - * @param {Object} objectDef - */ - serializeUserData: function ( object, objectDef ) { + } + /** + * Serializes a userData. + * + * @param {THREE.Object3D|THREE.Material} object + * @param {Object} objectDef + */ - if ( Object.keys( object.userData ).length === 0 ) return; - var options = this.options; - var extensionsUsed = this.extensionsUsed; - try { + serializeUserData( object, objectDef ) { - var json = JSON.parse( JSON.stringify( object.userData ) ); + if ( Object.keys( object.userData ).length === 0 ) return; + const options = this.options; + const extensionsUsed = this.extensionsUsed; - if ( options.includeCustomExtensions && json.gltfExtensions ) { + try { - if ( objectDef.extensions === undefined ) objectDef.extensions = {}; + const json = JSON.parse( JSON.stringify( object.userData ) ); - for ( var extensionName in json.gltfExtensions ) { + if ( options.includeCustomExtensions && json.gltfExtensions ) { - objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; - extensionsUsed[ extensionName ] = true; + if ( objectDef.extensions === undefined ) objectDef.extensions = {}; - } + for ( const extensionName in json.gltfExtensions ) { - delete json.gltfExtensions; + objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; + extensionsUsed[ extensionName ] = true; } - if ( Object.keys( json ).length > 0 ) objectDef.extras = json; + delete json.gltfExtensions; - } catch ( error ) { + } - console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + if ( Object.keys( json ).length > 0 ) objectDef.extras = json; - } + } catch ( error ) { - }, + console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); - /** - * Assign and return a temporal unique id for an object - * especially which doesn't have .uuid - * @param {Object} object - * @return {Integer} - */ - getUID: function ( object ) { + } - if ( ! this.uids.has( object ) ) this.uids.set( object, this.uid ++ ); - return this.uids.get( object ); + } + /** + * Assign and return a temporal unique id for an object + * especially which doesn't have .uuid + * @param {Object} object + * @return {Integer} + */ - }, - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {Boolean} - */ - isNormalizedNormalAttribute: function ( normal ) { + getUID( object ) { - var cache = this.cache; - if ( cache.attributesNormalized.has( normal ) ) return false; - var v = new THREE.Vector3(); + if ( ! this.uids.has( object ) ) this.uids.set( object, this.uid ++ ); + return this.uids.get( object ); - for ( var i = 0, il = normal.count; i < il; i ++ ) { + } + /** + * Checks if normal attribute values are normalized. + * + * @param {BufferAttribute} normal + * @returns {Boolean} + */ - // 0.0005 is from glTF-validator - if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; - } + isNormalizedNormalAttribute( normal ) { + + const cache = this.cache; + if ( cache.attributesNormalized.has( normal ) ) return false; + const v = new THREE.Vector3(); + + for ( let i = 0, il = normal.count; i < il; i ++ ) { + + // 0.0005 is from glTF-validator + if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; - return true; + } - }, + return true; - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - createNormalizedNormalAttribute: function ( normal ) { + } + /** + * Creates normalized normal buffer attribute. + * + * @param {BufferAttribute} normal + * @returns {BufferAttribute} + * + */ - var cache = this.cache; - if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); - var attribute = normal.clone(); - var v = new THREE.Vector3(); - for ( var i = 0, il = attribute.count; i < il; i ++ ) { + createNormalizedNormalAttribute( normal ) { - v.fromBufferAttribute( attribute, i ); + const cache = this.cache; + if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); + const attribute = normal.clone(); + const v = new THREE.Vector3(); - if ( v.x === 0 && v.y === 0 && v.z === 0 ) { + for ( let i = 0, il = attribute.count; i < il; i ++ ) { - // if values can't be normalized set (1, 0, 0) - v.setX( 1.0 ); + v.fromBufferAttribute( attribute, i ); - } else { + if ( v.x === 0 && v.y === 0 && v.z === 0 ) { - v.normalize(); + // if values can't be normalized set (1, 0, 0) + v.setX( 1.0 ); - } + } else { - attribute.setXYZ( i, v.x, v.y, v.z ); + v.normalize(); } - cache.attributesNormalized.set( normal, attribute ); - return attribute; + attribute.setXYZ( i, v.x, v.y, v.z ); - }, + } - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - * - * @param {Object} mapDef - * @param {THREE.Texture} texture - */ - applyTextureTransform: function ( mapDef, texture ) { + cache.attributesNormalized.set( normal, attribute ); + return attribute; - var didTransform = false; - var transformDef = {}; + } + /** + * Applies a texture transform, if present, to the map definition. Requires + * the KHR_texture_transform extension. + * + * @param {Object} mapDef + * @param {THREE.Texture} texture + */ - if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { - transformDef.offset = texture.offset.toArray(); - didTransform = true; + applyTextureTransform( mapDef, texture ) { - } + let didTransform = false; + const transformDef = {}; - if ( texture.rotation !== 0 ) { + if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { - transformDef.rotation = texture.rotation; - didTransform = true; + transformDef.offset = texture.offset.toArray(); + didTransform = true; - } + } - if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { + if ( texture.rotation !== 0 ) { - transformDef.scale = texture.repeat.toArray(); - didTransform = true; + transformDef.rotation = texture.rotation; + didTransform = true; - } + } - if ( didTransform ) { + if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { - mapDef.extensions = mapDef.extensions || {}; - mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; - this.extensionsUsed[ 'KHR_texture_transform' ] = true; + transformDef.scale = texture.repeat.toArray(); + didTransform = true; - } + } - }, + if ( didTransform ) { - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {Integer} - */ - processBuffer: function ( buffer ) { + mapDef.extensions = mapDef.extensions || {}; + mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; + this.extensionsUsed[ 'KHR_texture_transform' ] = true; - var json = this.json; - var buffers = this.buffers; - if ( ! json.buffers ) json.buffers = [ { - byteLength: 0 - } ]; // All buffers are merged before export. + } - buffers.push( buffer ); - return 0; + } + /** + * Process a buffer to append to the default one. + * @param {ArrayBuffer} buffer + * @return {Integer} + */ - }, - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} target (Optional) Target usage of the BufferView - * @return {Object} - */ - processBufferView: function ( attribute, componentType, start, count, target ) { + processBuffer( buffer ) { - var json = this.json; - if ( ! json.bufferViews ) json.bufferViews = []; // Create a new dataview and dump the attribute's array into it + const json = this.json; + const buffers = this.buffers; + if ( ! json.buffers ) json.buffers = [ { + byteLength: 0 + } ]; // All buffers are merged before export. - var componentSize; + buffers.push( buffer ); + return 0; - if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + } + /** + * Process and generate a BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} target (Optional) Target usage of the BufferView + * @return {Object} + */ - componentSize = 1; - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + processBufferView( attribute, componentType, start, count, target ) { - componentSize = 2; + const json = this.json; + if ( ! json.bufferViews ) json.bufferViews = []; // Create a new dataview and dump the attribute's array into it - } else { + let componentSize; - componentSize = 4; + if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - } + componentSize = 1; - var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); - var dataView = new DataView( new ArrayBuffer( byteLength ) ); - var offset = 0; + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - for ( var i = start; i < start + count; i ++ ) { + componentSize = 2; - for ( var a = 0; a < attribute.itemSize; a ++ ) { + } else { - var value; + componentSize = 4; - if ( attribute.itemSize > 4 ) { + } - // no support for interleaved data for itemSize > 4 - value = attribute.array[ i * attribute.itemSize + a ]; + const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); + const dataView = new DataView( new ArrayBuffer( byteLength ) ); + let offset = 0; - } else { + for ( let i = start; i < start + count; i ++ ) { - if ( a === 0 ) value = attribute.getX( i ); else if ( a === 1 ) value = attribute.getY( i ); else if ( a === 2 ) value = attribute.getZ( i ); else if ( a === 3 ) value = attribute.getW( i ); + for ( let a = 0; a < attribute.itemSize; a ++ ) { - } + let value; - if ( componentType === WEBGL_CONSTANTS.FLOAT ) { + if ( attribute.itemSize > 4 ) { - dataView.setFloat32( offset, value, true ); + // no support for interleaved data for itemSize > 4 + value = attribute.array[ i * attribute.itemSize + a ]; - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { + } else { - dataView.setUint32( offset, value, true ); + if ( a === 0 ) value = attribute.getX( i ); else if ( a === 1 ) value = attribute.getY( i ); else if ( a === 2 ) value = attribute.getZ( i ); else if ( a === 3 ) value = attribute.getW( i ); - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + } - dataView.setUint16( offset, value, true ); + if ( componentType === WEBGL_CONSTANTS.FLOAT ) { - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + dataView.setFloat32( offset, value, true ); - dataView.setUint8( offset, value ); + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { - } + dataView.setUint32( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + + dataView.setUint16( offset, value, true ); + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - offset += componentSize; + dataView.setUint8( offset, value ); } + offset += componentSize; + } - var bufferViewDef = { - buffer: this.processBuffer( dataView.buffer ), - byteOffset: this.byteOffset, - byteLength: byteLength - }; - if ( target !== undefined ) bufferViewDef.target = target; + } - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + const bufferViewDef = { + buffer: this.processBuffer( dataView.buffer ), + byteOffset: this.byteOffset, + byteLength: byteLength + }; + if ( target !== undefined ) bufferViewDef.target = target; - // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = attribute.itemSize * componentSize; + if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { - } + // Only define byteStride for vertex attributes. + bufferViewDef.byteStride = attribute.itemSize * componentSize; - this.byteOffset += byteLength; - json.bufferViews.push( bufferViewDef ); // @TODO Merge bufferViews where possible. + } - var output = { - id: json.bufferViews.length - 1, - byteLength: 0 - }; - return output; + this.byteOffset += byteLength; + json.bufferViews.push( bufferViewDef ); // @TODO Merge bufferViews where possible. - }, + const output = { + id: json.bufferViews.length - 1, + byteLength: 0 + }; + return output; - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise} - */ - processBufferViewImage: function ( blob ) { + } + /** + * Process and generate a BufferView from an image Blob. + * @param {Blob} blob + * @return {Promise} + */ - var writer = this; - var json = writer.json; - if ( ! json.bufferViews ) json.bufferViews = []; - return new Promise( function ( resolve ) { - var reader = new window.FileReader(); - reader.readAsArrayBuffer( blob ); + processBufferViewImage( blob ) { - reader.onloadend = function () { + const writer = this; + const json = writer.json; + if ( ! json.bufferViews ) json.bufferViews = []; + return new Promise( function ( resolve ) { - var buffer = getPaddedArrayBuffer( reader.result ); - var bufferViewDef = { - buffer: writer.processBuffer( buffer ), - byteOffset: writer.byteOffset, - byteLength: buffer.byteLength - }; - writer.byteOffset += buffer.byteLength; - resolve( json.bufferViews.push( bufferViewDef ) - 1 ); + const reader = new window.FileReader(); + reader.readAsArrayBuffer( blob ); - }; + reader.onloadend = function () { - } ); + const buffer = getPaddedArrayBuffer( reader.result ); + const bufferViewDef = { + buffer: writer.processBuffer( buffer ), + byteOffset: writer.byteOffset, + byteLength: buffer.byteLength + }; + writer.byteOffset += buffer.byteLength; + resolve( json.bufferViews.push( bufferViewDef ) - 1 ); - }, - - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range - * @param {Integer} start (Optional) - * @param {Integer} count (Optional) - * @return {Integer|null} Index of the processed accessor on the "accessors" array - */ - processAccessor: function ( attribute, geometry, start, count ) { - - var options = this.options; - var json = this.json; - var types = { - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 16: 'MAT4' }; - var componentType; // Detect the component type of the attribute array (float, uint or ushort) - if ( attribute.array.constructor === Float32Array ) { - - componentType = WEBGL_CONSTANTS.FLOAT; + } ); - } else if ( attribute.array.constructor === Uint32Array ) { + } + /** + * Process attribute to generate an accessor + * @param {BufferAttribute} attribute Attribute to process + * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range + * @param {Integer} start (Optional) + * @param {Integer} count (Optional) + * @return {Integer|null} Index of the processed accessor on the "accessors" array + */ - componentType = WEBGL_CONSTANTS.UNSIGNED_INT; - } else if ( attribute.array.constructor === Uint16Array ) { + processAccessor( attribute, geometry, start, count ) { - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; + const options = this.options; + const json = this.json; + const types = { + 1: 'SCALAR', + 2: 'VEC2', + 3: 'VEC3', + 4: 'VEC4', + 16: 'MAT4' + }; + let componentType; // Detect the component type of the attribute array (float, uint or ushort) - } else if ( attribute.array.constructor === Uint8Array ) { + if ( attribute.array.constructor === Float32Array ) { - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; + componentType = WEBGL_CONSTANTS.FLOAT; - } else { + } else if ( attribute.array.constructor === Uint32Array ) { - throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); + componentType = WEBGL_CONSTANTS.UNSIGNED_INT; - } + } else if ( attribute.array.constructor === Uint16Array ) { - if ( start === undefined ) start = 0; - if ( count === undefined ) count = attribute.count; // @TODO Indexed buffer geometry with drawRange not supported yet + componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; - if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) { + } else if ( attribute.array.constructor === Uint8Array ) { - var end = start + count; - var end2 = geometry.drawRange.count === Infinity ? attribute.count : geometry.drawRange.start + geometry.drawRange.count; - start = Math.max( start, geometry.drawRange.start ); - count = Math.min( end, end2 ) - start; - if ( count < 0 ) count = 0; + componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; - } // Skip creating an accessor if the attribute doesn't have data to export + } else { + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); - if ( count === 0 ) return null; - var minMax = getMinMax( attribute, start, count ); - var bufferViewTarget; // If geometry isn't provided, don't infer the target usage of the bufferView. For - // animation samplers, target must not be set. + } - if ( geometry !== undefined ) { + if ( start === undefined ) start = 0; + if ( count === undefined ) count = attribute.count; // @TODO Indexed buffer geometry with drawRange not supported yet - bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; + if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) { - } + const end = start + count; + const end2 = geometry.drawRange.count === Infinity ? attribute.count : geometry.drawRange.start + geometry.drawRange.count; + start = Math.max( start, geometry.drawRange.start ); + count = Math.min( end, end2 ) - start; + if ( count < 0 ) count = 0; - var bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); - var accessorDef = { - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[ attribute.itemSize ] - }; - if ( attribute.normalized === true ) accessorDef.normalized = true; - if ( ! json.accessors ) json.accessors = []; - return json.accessors.push( accessorDef ) - 1; - - }, - - /** - * Process image - * @param {Image} image to process - * @param {Integer} format of the image (e.g. THREE.RGBFormat, THREE.RGBAFormat etc) - * @param {Boolean} flipY before writing out the image - * @return {Integer} Index of the processed texture in the "images" array - */ - processImage: function ( image, format, flipY ) { - - var writer = this; - var cache = writer.cache; - var json = writer.json; - var options = writer.options; - var pending = writer.pending; - if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); - var cachedImages = cache.images.get( image ); - var mimeType = format === THREE.RGBAFormat ? 'image/png' : 'image/jpeg'; - var key = mimeType + ':flipY/' + flipY.toString(); - if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; - if ( ! json.images ) json.images = []; - var imageDef = { - mimeType: mimeType - }; + } // Skip creating an accessor if the attribute doesn't have data to export - if ( options.embedImages ) { - var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' ); - canvas.width = Math.min( image.width, options.maxTextureSize ); - canvas.height = Math.min( image.height, options.maxTextureSize ); - var ctx = canvas.getContext( '2d' ); + if ( count === 0 ) return null; + const minMax = getMinMax( attribute, start, count ); + let bufferViewTarget; // If geometry isn't provided, don't infer the target usage of the bufferView. For + // animation samplers, target must not be set. - if ( flipY === true ) { + if ( geometry !== undefined ) { - ctx.translate( 0, canvas.height ); - ctx.scale( 1, - 1 ); + bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; - } + } - if ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement || typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas || typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) { + const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); + const accessorDef = { + bufferView: bufferView.id, + byteOffset: bufferView.byteOffset, + componentType: componentType, + count: count, + max: minMax.max, + min: minMax.min, + type: types[ attribute.itemSize ] + }; + if ( attribute.normalized === true ) accessorDef.normalized = true; + if ( ! json.accessors ) json.accessors = []; + return json.accessors.push( accessorDef ) - 1; - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + } + /** + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (e.g. THREE.RGBFormat, THREE.RGBAFormat etc) + * @param {Boolean} flipY before writing out the image + * @return {Integer} Index of the processed texture in the "images" array + */ - } else { - if ( format !== THREE.RGBAFormat && format !== THREE.RGBFormat ) { + processImage( image, format, flipY ) { + + const writer = this; + const cache = writer.cache; + const json = writer.json; + const options = writer.options; + const pending = writer.pending; + if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); + const cachedImages = cache.images.get( image ); + const mimeType = format === THREE.RGBAFormat ? 'image/png' : 'image/jpeg'; + const key = mimeType + ':flipY/' + flipY.toString(); + if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; + if ( ! json.images ) json.images = []; + const imageDef = { + mimeType: mimeType + }; - console.error( 'GLTFExporter: Only RGB and RGBA formats are supported.' ); + if ( options.embedImages ) { - } + const canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' ); + canvas.width = Math.min( image.width, options.maxTextureSize ); + canvas.height = Math.min( image.height, options.maxTextureSize ); + const ctx = canvas.getContext( '2d' ); - if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { + if ( flipY === true ) { - console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); + ctx.translate( 0, canvas.height ); + ctx.scale( 1, - 1 ); - } + } - var data = image.data; + if ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement || typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas || typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) { - if ( format === THREE.RGBFormat ) { + ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); - data = new Uint8ClampedArray( image.height * image.width * 4 ); + } else { - for ( var i = 0, j = 0; i < data.length; i += 4, j += 3 ) { + if ( format !== THREE.RGBAFormat && format !== THREE.RGBFormat ) { - data[ i + 0 ] = image.data[ j + 0 ]; - data[ i + 1 ] = image.data[ j + 1 ]; - data[ i + 2 ] = image.data[ j + 2 ]; - data[ i + 3 ] = 255; + console.error( 'GLTFExporter: Only RGB and RGBA formats are supported.' ); - } + } - } + if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { - ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); + console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); } - if ( options.binary === true ) { + let data = image.data; - pending.push( new Promise( function ( resolve ) { + if ( format === THREE.RGBFormat ) { - canvas.toBlob( function ( blob ) { + data = new Uint8ClampedArray( image.height * image.width * 4 ); - writer.processBufferViewImage( blob ).then( function ( bufferViewIndex ) { + for ( let i = 0, j = 0; i < data.length; i += 4, j += 3 ) { - imageDef.bufferView = bufferViewIndex; - resolve(); + data[ i + 0 ] = image.data[ j + 0 ]; + data[ i + 1 ] = image.data[ j + 1 ]; + data[ i + 2 ] = image.data[ j + 2 ]; + data[ i + 3 ] = 255; - } ); + } - }, mimeType ); + } - } ) ); + ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); - } else { + } - imageDef.uri = canvas.toDataURL( mimeType ); + if ( options.binary === true ) { - } + pending.push( new Promise( function ( resolve ) { + + canvas.toBlob( function ( blob ) { + + writer.processBufferViewImage( blob ).then( function ( bufferViewIndex ) { + + imageDef.bufferView = bufferViewIndex; + resolve(); + + } ); + + }, mimeType ); + + } ) ); } else { - imageDef.uri = image.src; + imageDef.uri = canvas.toDataURL( mimeType ); } - var index = json.images.push( imageDef ) - 1; - cachedImages[ key ] = index; - return index; - - }, - - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {Integer} Index of the processed texture in the "samplers" array - */ - processSampler: function ( map ) { - - var json = this.json; - if ( ! json.samplers ) json.samplers = []; - var samplerDef = { - magFilter: THREE_TO_WEBGL[ map.magFilter ], - minFilter: THREE_TO_WEBGL[ map.minFilter ], - wrapS: THREE_TO_WEBGL[ map.wrapS ], - wrapT: THREE_TO_WEBGL[ map.wrapT ] - }; - return json.samplers.push( samplerDef ) - 1; - - }, - - /** - * Process texture - * @param {Texture} map Map to process - * @return {Integer} Index of the processed texture in the "textures" array - */ - processTexture: function ( map ) { - - var cache = this.cache; - var json = this.json; - if ( cache.textures.has( map ) ) return cache.textures.get( map ); - if ( ! json.textures ) json.textures = []; - var textureDef = { - sampler: this.processSampler( map ), - source: this.processImage( map.image, map.format, map.flipY ) - }; - if ( map.name ) textureDef.name = map.name; + } else { - this._invokeAll( function ( ext ) { + imageDef.uri = image.src; - ext.writeTexture && ext.writeTexture( map, textureDef ); + } - } ); + const index = json.images.push( imageDef ) - 1; + cachedImages[ key ] = index; + return index; - var index = json.textures.push( textureDef ) - 1; - cache.textures.set( map, index ); - return index; + } + /** + * Process sampler + * @param {Texture} map Texture to process + * @return {Integer} Index of the processed texture in the "samplers" array + */ - }, - /** - * Process material - * @param {THREE.Material} material Material to process - * @return {Integer|null} Index of the processed material in the "materials" array - */ - processMaterial: function ( material ) { + processSampler( map ) { - var cache = this.cache; - var json = this.json; - if ( cache.materials.has( material ) ) return cache.materials.get( material ); + const json = this.json; + if ( ! json.samplers ) json.samplers = []; + const samplerDef = { + magFilter: THREE_TO_WEBGL[ map.magFilter ], + minFilter: THREE_TO_WEBGL[ map.minFilter ], + wrapS: THREE_TO_WEBGL[ map.wrapS ], + wrapT: THREE_TO_WEBGL[ map.wrapT ] + }; + return json.samplers.push( samplerDef ) - 1; - if ( material.isShaderMaterial ) { + } + /** + * Process texture + * @param {Texture} map Map to process + * @return {Integer} Index of the processed texture in the "textures" array + */ - console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); - return null; - } + processTexture( map ) { - if ( ! json.materials ) json.materials = []; // @QUESTION Should we avoid including any attribute that has the default value? + const cache = this.cache; + const json = this.json; + if ( cache.textures.has( map ) ) return cache.textures.get( map ); + if ( ! json.textures ) json.textures = []; + const textureDef = { + sampler: this.processSampler( map ), + source: this.processImage( map.image, map.format, map.flipY ) + }; + if ( map.name ) textureDef.name = map.name; - var materialDef = { - pbrMetallicRoughness: {} - }; + this._invokeAll( function ( ext ) { - if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { + ext.writeTexture && ext.writeTexture( map, textureDef ); - console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); + } ); - } // pbrMetallicRoughness.baseColorFactor + const index = json.textures.push( textureDef ) - 1; + cache.textures.set( map, index ); + return index; + } + /** + * Process material + * @param {THREE.Material} material Material to process + * @return {Integer|null} Index of the processed material in the "materials" array + */ - var color = material.color.toArray().concat( [ material.opacity ] ); - if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { + processMaterial( material ) { - materialDef.pbrMetallicRoughness.baseColorFactor = color; + const cache = this.cache; + const json = this.json; + if ( cache.materials.has( material ) ) return cache.materials.get( material ); - } + if ( material.isShaderMaterial ) { - if ( material.isMeshStandardMaterial ) { + console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); + return null; - materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; - materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; + } - } else { + if ( ! json.materials ) json.materials = []; // @QUESTION Should we avoid including any attribute that has the default value? - materialDef.pbrMetallicRoughness.metallicFactor = 0.5; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; + const materialDef = { + pbrMetallicRoughness: {} + }; - } // pbrMetallicRoughness.metallicRoughnessTexture + if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { + console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); - if ( material.metalnessMap || material.roughnessMap ) { + } // pbrMetallicRoughness.baseColorFactor - if ( material.metalnessMap === material.roughnessMap ) { - var metalRoughMapDef = { - index: this.processTexture( material.metalnessMap ) - }; - this.applyTextureTransform( metalRoughMapDef, material.metalnessMap ); - materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + const color = material.color.toArray().concat( [ material.opacity ] ); - } else { + if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { - console.warn( 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' ); + materialDef.pbrMetallicRoughness.baseColorFactor = color; - } + } + + if ( material.isMeshStandardMaterial ) { - } // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture + materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; + materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; + } else { - if ( material.map ) { + materialDef.pbrMetallicRoughness.metallicFactor = 0.5; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; - var baseColorMapDef = { - index: this.processTexture( material.map ) + } // pbrMetallicRoughness.metallicRoughnessTexture + + + if ( material.metalnessMap || material.roughnessMap ) { + + if ( material.metalnessMap === material.roughnessMap ) { + + const metalRoughMapDef = { + index: this.processTexture( material.metalnessMap ) }; - this.applyTextureTransform( baseColorMapDef, material.map ); - materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + this.applyTextureTransform( metalRoughMapDef, material.metalnessMap ); + materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + + } else { + + console.warn( 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' ); } - if ( material.emissive ) { + } // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture - // emissiveFactor - var emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray(); - if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) { + if ( material.map ) { - materialDef.emissiveFactor = emissive; + const baseColorMapDef = { + index: this.processTexture( material.map ) + }; + this.applyTextureTransform( baseColorMapDef, material.map ); + materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; - } // emissiveTexture + } + if ( material.emissive ) { - if ( material.emissiveMap ) { + // emissiveFactor + const emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray(); - var emissiveMapDef = { - index: this.processTexture( material.emissiveMap ) - }; - this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); - materialDef.emissiveTexture = emissiveMapDef; + if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) { - } + materialDef.emissiveFactor = emissive; - } // normalTexture + } // emissiveTexture - if ( material.normalMap ) { + if ( material.emissiveMap ) { - var normalMapDef = { - index: this.processTexture( material.normalMap ) + const emissiveMapDef = { + index: this.processTexture( material.emissiveMap ) }; + this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); + materialDef.emissiveTexture = emissiveMapDef; - if ( material.normalScale && material.normalScale.x !== - 1 ) { + } - if ( material.normalScale.x !== material.normalScale.y ) { + } // normalTexture - console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' ); - } + if ( material.normalMap ) { + + const normalMapDef = { + index: this.processTexture( material.normalMap ) + }; - normalMapDef.scale = material.normalScale.x; + if ( material.normalScale && material.normalScale.x !== - 1 ) { + + if ( material.normalScale.x !== material.normalScale.y ) { + + console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' ); } - this.applyTextureTransform( normalMapDef, material.normalMap ); - materialDef.normalTexture = normalMapDef; + normalMapDef.scale = material.normalScale.x; - } // occlusionTexture + } + this.applyTextureTransform( normalMapDef, material.normalMap ); + materialDef.normalTexture = normalMapDef; - if ( material.aoMap ) { + } // occlusionTexture - var occlusionMapDef = { - index: this.processTexture( material.aoMap ), - texCoord: 1 - }; - if ( material.aoMapIntensity !== 1.0 ) { + if ( material.aoMap ) { - occlusionMapDef.strength = material.aoMapIntensity; + const occlusionMapDef = { + index: this.processTexture( material.aoMap ), + texCoord: 1 + }; - } + if ( material.aoMapIntensity !== 1.0 ) { - this.applyTextureTransform( occlusionMapDef, material.aoMap ); - materialDef.occlusionTexture = occlusionMapDef; + occlusionMapDef.strength = material.aoMapIntensity; - } // alphaMode + } + this.applyTextureTransform( occlusionMapDef, material.aoMap ); + materialDef.occlusionTexture = occlusionMapDef; - if ( material.transparent ) { + } // alphaMode - materialDef.alphaMode = 'BLEND'; - } else { + if ( material.transparent ) { - if ( material.alphaTest > 0.0 ) { + materialDef.alphaMode = 'BLEND'; - materialDef.alphaMode = 'MASK'; - materialDef.alphaCutoff = material.alphaTest; + } else { - } + if ( material.alphaTest > 0.0 ) { - } // doubleSided + materialDef.alphaMode = 'MASK'; + materialDef.alphaCutoff = material.alphaTest; + } - if ( material.side === THREE.DoubleSide ) materialDef.doubleSided = true; - if ( material.name !== '' ) materialDef.name = material.name; - this.serializeUserData( material, materialDef ); + } // doubleSided - this._invokeAll( function ( ext ) { - ext.writeMaterial && ext.writeMaterial( material, materialDef ); + if ( material.side === THREE.DoubleSide ) materialDef.doubleSided = true; + if ( material.name !== '' ) materialDef.name = material.name; + this.serializeUserData( material, materialDef ); - } ); + this._invokeAll( function ( ext ) { - var index = json.materials.push( materialDef ) - 1; - cache.materials.set( material, index ); - return index; + ext.writeMaterial && ext.writeMaterial( material, materialDef ); - }, + } ); - /** - * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Integer|null} Index of the processed mesh in the "meshes" array - */ - processMesh: function ( mesh ) { + const index = json.materials.push( materialDef ) - 1; + cache.materials.set( material, index ); + return index; - var cache = this.cache; - var json = this.json; - var meshCacheKeyParts = [ mesh.geometry.uuid ]; + } + /** + * Process mesh + * @param {THREE.Mesh} mesh Mesh to process + * @return {Integer|null} Index of the processed mesh in the "meshes" array + */ - if ( Array.isArray( mesh.material ) ) { - for ( var i = 0, l = mesh.material.length; i < l; i ++ ) { + processMesh( mesh ) { - meshCacheKeyParts.push( mesh.material[ i ].uuid ); + const cache = this.cache; + const json = this.json; + const meshCacheKeyParts = [ mesh.geometry.uuid ]; - } + if ( Array.isArray( mesh.material ) ) { - } else { + for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { - meshCacheKeyParts.push( mesh.material.uuid ); + meshCacheKeyParts.push( mesh.material[ i ].uuid ); } - var meshCacheKey = meshCacheKeyParts.join( ':' ); - if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); - var geometry = mesh.geometry; - var mode; // Use the correct mode + } else { - if ( mesh.isLineSegments ) { + meshCacheKeyParts.push( mesh.material.uuid ); - mode = WEBGL_CONSTANTS.LINES; + } - } else if ( mesh.isLineLoop ) { + const meshCacheKey = meshCacheKeyParts.join( ':' ); + if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); + const geometry = mesh.geometry; + let mode; // Use the correct mode - mode = WEBGL_CONSTANTS.LINE_LOOP; + if ( mesh.isLineSegments ) { - } else if ( mesh.isLine ) { + mode = WEBGL_CONSTANTS.LINES; - mode = WEBGL_CONSTANTS.LINE_STRIP; + } else if ( mesh.isLineLoop ) { - } else if ( mesh.isPoints ) { + mode = WEBGL_CONSTANTS.LINE_LOOP; - mode = WEBGL_CONSTANTS.POINTS; + } else if ( mesh.isLine ) { - } else { + mode = WEBGL_CONSTANTS.LINE_STRIP; - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; + } else if ( mesh.isPoints ) { - } + mode = WEBGL_CONSTANTS.POINTS; - if ( geometry.isBufferGeometry !== true ) { + } else { - throw new Error( 'THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.' ); + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; - } + } - var meshDef = {}; - var attributes = {}; - var primitives = []; - var targets = []; // Conversion between attributes names in threejs and gltf spec - - var nameConversion = { - uv: 'TEXCOORD_0', - uv2: 'TEXCOORD_1', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0' - }; - var originalNormal = geometry.getAttribute( 'normal' ); + if ( geometry.isBufferGeometry !== true ) { - if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { + throw new Error( 'THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.' ); - console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); - geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); + } - } // @QUESTION Detect if .vertexColors = true? - // For every attribute create an accessor + const meshDef = {}; + const attributes = {}; + const primitives = []; + const targets = []; // Conversion between attributes names in threejs and gltf spec + + const nameConversion = { + uv: 'TEXCOORD_0', + uv2: 'TEXCOORD_1', + color: 'COLOR_0', + skinWeight: 'WEIGHTS_0', + skinIndex: 'JOINTS_0' + }; + const originalNormal = geometry.getAttribute( 'normal' ); + if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { - var modifiedAttribute = null; + console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); + geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); - for ( var attributeName in geometry.attributes ) { + } // @QUESTION Detect if .vertexColors = true? + // For every attribute create an accessor - // Ignore morph target attributes, which are exported later. - if ( attributeName.substr( 0, 5 ) === 'morph' ) continue; - var attribute = geometry.attributes[ attributeName ]; - attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); // Prefix all geometry attributes except the ones specifically - // listed in the spec; non-spec attributes are considered custom. - var validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; - if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; + let modifiedAttribute = null; - if ( cache.attributes.has( this.getUID( attribute ) ) ) { + for ( let attributeName in geometry.attributes ) { - attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; + // Ignore morph target attributes, which are exported later. + if ( attributeName.substr( 0, 5 ) === 'morph' ) continue; + const attribute = geometry.attributes[ attributeName ]; + attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); // Prefix all geometry attributes except the ones specifically + // listed in the spec; non-spec attributes are considered custom. - } // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; + if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; + if ( cache.attributes.has( this.getUID( attribute ) ) ) { - modifiedAttribute = null; - var array = attribute.array; + attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; - if ( attributeName === 'JOINTS_0' && ! ( array instanceof Uint16Array ) && ! ( array instanceof Uint8Array ) ) { + } // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. - console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); - modifiedAttribute = new THREE.BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); - } + modifiedAttribute = null; + const array = attribute.array; - var accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); + if ( attributeName === 'JOINTS_0' && ! ( array instanceof Uint16Array ) && ! ( array instanceof Uint8Array ) ) { - if ( accessor !== null ) { + console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); + modifiedAttribute = new THREE.BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); - attributes[ attributeName ] = accessor; - cache.attributes.set( this.getUID( attribute ), accessor ); + } - } + const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); + + if ( accessor !== null ) { + + attributes[ attributeName ] = accessor; + cache.attributes.set( this.getUID( attribute ), accessor ); } - if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); // Skip if no exportable attributes found + } - if ( Object.keys( attributes ).length === 0 ) return null; // Morph targets + if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); // Skip if no exportable attributes found - if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { + if ( Object.keys( attributes ).length === 0 ) return null; // Morph targets - var weights = []; - var targetNames = []; - var reverseDictionary = {}; + if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { - if ( mesh.morphTargetDictionary !== undefined ) { + const weights = []; + const targetNames = []; + const reverseDictionary = {}; - for ( var key in mesh.morphTargetDictionary ) { + if ( mesh.morphTargetDictionary !== undefined ) { - reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; + for ( const key in mesh.morphTargetDictionary ) { - } + reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; } - for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - - var target = {}; - var warned = false; + } - for ( var attributeName in geometry.morphAttributes ) { + for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. - if ( attributeName !== 'position' && attributeName !== 'normal' ) { + const target = {}; + let warned = false; - if ( ! warned ) { + for ( const attributeName in geometry.morphAttributes ) { - console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); - warned = true; + // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. + // Three.js doesn't support TANGENT yet. + if ( attributeName !== 'position' && attributeName !== 'normal' ) { - } + if ( ! warned ) { - continue; + console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); + warned = true; } - var attribute = geometry.morphAttributes[ attributeName ][ i ]; - var gltfAttributeName = attributeName.toUpperCase(); // Three.js morph attribute has absolute values while the one of glTF has relative values. - // - // glTF 2.0 Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets + continue; - var baseAttribute = geometry.attributes[ attributeName ]; + } - if ( cache.attributes.has( this.getUID( attribute ) ) ) { + const attribute = geometry.morphAttributes[ attributeName ][ i ]; + const gltfAttributeName = attributeName.toUpperCase(); // Three.js morph attribute has absolute values while the one of glTF has relative values. + // + // glTF 2.0 Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets - target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; + const baseAttribute = geometry.attributes[ attributeName ]; - } // Clones attribute not to override + if ( cache.attributes.has( this.getUID( attribute ) ) ) { + target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; - var relativeAttribute = attribute.clone(); + } // Clones attribute not to override - if ( ! geometry.morphTargetsRelative ) { - for ( var j = 0, jl = attribute.count; j < jl; j ++ ) { + const relativeAttribute = attribute.clone(); - relativeAttribute.setXYZ( j, attribute.getX( j ) - baseAttribute.getX( j ), attribute.getY( j ) - baseAttribute.getY( j ), attribute.getZ( j ) - baseAttribute.getZ( j ) ); + if ( ! geometry.morphTargetsRelative ) { - } + for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { - } + relativeAttribute.setXYZ( j, attribute.getX( j ) - baseAttribute.getX( j ), attribute.getY( j ) - baseAttribute.getY( j ), attribute.getZ( j ) - baseAttribute.getZ( j ) ); - target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); - cache.attributes.set( this.getUID( baseAttribute ), target[ gltfAttributeName ] ); + } } - targets.push( target ); - weights.push( mesh.morphTargetInfluences[ i ] ); - if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); + target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); + cache.attributes.set( this.getUID( baseAttribute ), target[ gltfAttributeName ] ); } - meshDef.weights = weights; + targets.push( target ); + weights.push( mesh.morphTargetInfluences[ i ] ); + if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); - if ( targetNames.length > 0 ) { + } - meshDef.extras = {}; - meshDef.extras.targetNames = targetNames; + meshDef.weights = weights; - } + if ( targetNames.length > 0 ) { - } + meshDef.extras = {}; + meshDef.extras.targetNames = targetNames; - var isMultiMaterial = Array.isArray( mesh.material ); - if ( isMultiMaterial && geometry.groups.length === 0 ) return null; - var materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - var groups = isMultiMaterial ? geometry.groups : [ { - materialIndex: 0, - start: undefined, - count: undefined - } ]; + } - for ( var i = 0, il = groups.length; i < il; i ++ ) { + } - var primitive = { - mode: mode, - attributes: attributes - }; - this.serializeUserData( geometry, primitive ); - if ( targets.length > 0 ) primitive.targets = targets; + const isMultiMaterial = Array.isArray( mesh.material ); + if ( isMultiMaterial && geometry.groups.length === 0 ) return null; + const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; + const groups = isMultiMaterial ? geometry.groups : [ { + materialIndex: 0, + start: undefined, + count: undefined + } ]; - if ( geometry.index !== null ) { + for ( let i = 0, il = groups.length; i < il; i ++ ) { - var cacheKey = this.getUID( geometry.index ); + const primitive = { + mode: mode, + attributes: attributes + }; + this.serializeUserData( geometry, primitive ); + if ( targets.length > 0 ) primitive.targets = targets; - if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { + if ( geometry.index !== null ) { - cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; + let cacheKey = this.getUID( geometry.index ); - } + if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { - if ( cache.attributes.has( cacheKey ) ) { + cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; - primitive.indices = cache.attributes.get( cacheKey ); + } - } else { + if ( cache.attributes.has( cacheKey ) ) { - primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); - cache.attributes.set( cacheKey, primitive.indices ); + primitive.indices = cache.attributes.get( cacheKey ); - } + } else { - if ( primitive.indices === null ) delete primitive.indices; + primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); + cache.attributes.set( cacheKey, primitive.indices ); } - var material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); - if ( material !== null ) primitive.material = material; - primitives.push( primitive ); + if ( primitive.indices === null ) delete primitive.indices; } - meshDef.primitives = primitives; - if ( ! json.meshes ) json.meshes = []; + const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); + if ( material !== null ) primitive.material = material; + primitives.push( primitive ); - this._invokeAll( function ( ext ) { + } - ext.writeMesh && ext.writeMesh( mesh, meshDef ); + meshDef.primitives = primitives; + if ( ! json.meshes ) json.meshes = []; - } ); + this._invokeAll( function ( ext ) { - var index = json.meshes.push( meshDef ) - 1; - cache.meshes.set( meshCacheKey, index ); - return index; + ext.writeMesh && ext.writeMesh( mesh, meshDef ); - }, + } ); - /** - * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {Integer} Index of the processed mesh in the "camera" array - */ - processCamera: function ( camera ) { + const index = json.meshes.push( meshDef ) - 1; + cache.meshes.set( meshCacheKey, index ); + return index; - var json = this.json; - if ( ! json.cameras ) json.cameras = []; - var isOrtho = camera.isOrthographicCamera; - var cameraDef = { - type: isOrtho ? 'orthographic' : 'perspective' - }; + } + /** + * Process camera + * @param {THREE.Camera} camera Camera to process + * @return {Integer} Index of the processed mesh in the "camera" array + */ - if ( isOrtho ) { - cameraDef.orthographic = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + processCamera( camera ) { - } else { + const json = this.json; + if ( ! json.cameras ) json.cameras = []; + const isOrtho = camera.isOrthographicCamera; + const cameraDef = { + type: isOrtho ? 'orthographic' : 'perspective' + }; - cameraDef.perspective = { - aspectRatio: camera.aspect, - yfov: THREE.MathUtils.degToRad( camera.fov ), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + if ( isOrtho ) { - } // Question: Is saving "type" as name intentional? + cameraDef.orthographic = { + xmag: camera.right * 2, + ymag: camera.top * 2, + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; + } else { - if ( camera.name !== '' ) cameraDef.name = camera.type; - return json.cameras.push( cameraDef ) - 1; + cameraDef.perspective = { + aspectRatio: camera.aspect, + yfov: THREE.MathUtils.degToRad( camera.fov ), + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - }, + } // Question: Is saving "type" as name intentional? - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {THREE.AnimationClip} clip - * @param {THREE.Object3D} root - * @return {number|null} - */ - processAnimation: function ( clip, root ) { - var json = this.json; - var nodeMap = this.nodeMap; - if ( ! json.animations ) json.animations = []; - clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); - var tracks = clip.tracks; - var channels = []; - var samplers = []; + if ( camera.name !== '' ) cameraDef.name = camera.type; + return json.cameras.push( cameraDef ) - 1; - for ( var i = 0; i < tracks.length; ++ i ) { + } + /** + * Creates glTF animation entry from AnimationClip object. + * + * Status: + * - Only properties listed in PATH_PROPERTIES may be animated. + * + * @param {THREE.AnimationClip} clip + * @param {THREE.Object3D} root + * @return {number|null} + */ - var track = tracks[ i ]; - var trackBinding = THREE.PropertyBinding.parseTrackName( track.name ); - var trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName ); - var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - if ( trackBinding.objectName === 'bones' ) { + processAnimation( clip, root ) { - if ( trackNode.isSkinnedMesh === true ) { + const json = this.json; + const nodeMap = this.nodeMap; + if ( ! json.animations ) json.animations = []; + clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); + const tracks = clip.tracks; + const channels = []; + const samplers = []; - trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); + for ( let i = 0; i < tracks.length; ++ i ) { - } else { + const track = tracks[ i ]; + const trackBinding = THREE.PropertyBinding.parseTrackName( track.name ); + let trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName ); + const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - trackNode = undefined; + if ( trackBinding.objectName === 'bones' ) { - } + if ( trackNode.isSkinnedMesh === true ) { - } + trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); - if ( ! trackNode || ! trackProperty ) { + } else { - console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); - return null; + trackNode = undefined; } - var inputItemSize = 1; - var outputItemSize = track.values.length / track.times.length; + } - if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { + if ( ! trackNode || ! trackProperty ) { - outputItemSize /= trackNode.morphTargetInfluences.length; + console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); + return null; - } + } - var interpolation; // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - // Detecting glTF cubic spline interpolant by checking factory method's special property - // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return - // valid value from .getInterpolation(). + const inputItemSize = 1; + let outputItemSize = track.values.length / track.times.length; - if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { + if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { - interpolation = 'CUBICSPLINE'; // itemSize of CUBICSPLINE keyframe is 9 - // (VEC3 * 3: inTangent, splineVertex, and outTangent) - // but needs to be stored as VEC3 so dividing by 3 here. + outputItemSize /= trackNode.morphTargetInfluences.length; - outputItemSize /= 3; + } - } else if ( track.getInterpolation() === THREE.InterpolateDiscrete ) { + let interpolation; // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE + // Detecting glTF cubic spline interpolant by checking factory method's special property + // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return + // valid value from .getInterpolation(). - interpolation = 'STEP'; + if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { - } else { + interpolation = 'CUBICSPLINE'; // itemSize of CUBICSPLINE keyframe is 9 + // (VEC3 * 3: inTangent, splineVertex, and outTangent) + // but needs to be stored as VEC3 so dividing by 3 here. - interpolation = 'LINEAR'; + outputItemSize /= 3; - } + } else if ( track.getInterpolation() === THREE.InterpolateDiscrete ) { - samplers.push( { - input: this.processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ), - output: this.processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ), - interpolation: interpolation - } ); - channels.push( { - sampler: samplers.length - 1, - target: { - node: nodeMap.get( trackNode ), - path: trackProperty - } - } ); + interpolation = 'STEP'; + + } else { + + interpolation = 'LINEAR'; } - json.animations.push( { - name: clip.name || 'clip_' + json.animations.length, - samplers: samplers, - channels: channels + samplers.push( { + input: this.processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ), + output: this.processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ), + interpolation: interpolation + } ); + channels.push( { + sampler: samplers.length - 1, + target: { + node: nodeMap.get( trackNode ), + path: trackProperty + } } ); - return json.animations.length - 1; - }, + } - /** - * @param {THREE.Object3D} object - * @return {number|null} - */ - processSkin: function ( object ) { + json.animations.push( { + name: clip.name || 'clip_' + json.animations.length, + samplers: samplers, + channels: channels + } ); + return json.animations.length - 1; - var json = this.json; - var nodeMap = this.nodeMap; - var node = json.nodes[ nodeMap.get( object ) ]; - var skeleton = object.skeleton; - if ( skeleton === undefined ) return null; - var rootJoint = object.skeleton.bones[ 0 ]; - if ( rootJoint === undefined ) return null; - var joints = []; - var inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); - var temporaryBoneInverse = new THREE.Matrix4(); + } + /** + * @param {THREE.Object3D} object + * @return {number|null} + */ - for ( var i = 0; i < skeleton.bones.length; ++ i ) { - joints.push( nodeMap.get( skeleton.bones[ i ] ) ); - temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); - temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); + processSkin( object ) { - } + const json = this.json; + const nodeMap = this.nodeMap; + const node = json.nodes[ nodeMap.get( object ) ]; + const skeleton = object.skeleton; + if ( skeleton === undefined ) return null; + const rootJoint = object.skeleton.bones[ 0 ]; + if ( rootJoint === undefined ) return null; + const joints = []; + const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); + const temporaryBoneInverse = new THREE.Matrix4(); - if ( json.skins === undefined ) json.skins = []; - json.skins.push( { - inverseBindMatrices: this.processAccessor( new THREE.BufferAttribute( inverseBindMatrices, 16 ) ), - joints: joints, - skeleton: nodeMap.get( rootJoint ) - } ); - var skinIndex = node.skin = json.skins.length - 1; - return skinIndex; + for ( let i = 0; i < skeleton.bones.length; ++ i ) { - }, + joints.push( nodeMap.get( skeleton.bones[ i ] ) ); + temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); + temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); - /** - * Process Object3D node - * @param {THREE.Object3D} node Object3D to processNode - * @return {Integer} Index of the node in the nodes list - */ - processNode: function ( object ) { + } - var json = this.json; - var options = this.options; - var nodeMap = this.nodeMap; - if ( ! json.nodes ) json.nodes = []; - var nodeDef = {}; + if ( json.skins === undefined ) json.skins = []; + json.skins.push( { + inverseBindMatrices: this.processAccessor( new THREE.BufferAttribute( inverseBindMatrices, 16 ) ), + joints: joints, + skeleton: nodeMap.get( rootJoint ) + } ); + const skinIndex = node.skin = json.skins.length - 1; + return skinIndex; - if ( options.trs ) { + } + /** + * Process Object3D node + * @param {THREE.Object3D} node Object3D to processNode + * @return {Integer} Index of the node in the nodes list + */ - var rotation = object.quaternion.toArray(); - var position = object.position.toArray(); - var scale = object.scale.toArray(); - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { + processNode( object ) { - nodeDef.rotation = rotation; + const json = this.json; + const options = this.options; + const nodeMap = this.nodeMap; + if ( ! json.nodes ) json.nodes = []; + const nodeDef = {}; - } + if ( options.trs ) { - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { + const rotation = object.quaternion.toArray(); + const position = object.position.toArray(); + const scale = object.scale.toArray(); - nodeDef.translation = position; + if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - } + nodeDef.rotation = rotation; + + } - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { + if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - nodeDef.scale = scale; + nodeDef.translation = position; - } + } - } else { + if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - if ( object.matrixAutoUpdate ) { + nodeDef.scale = scale; - object.updateMatrix(); + } - } + } else { - if ( isIdentityMatrix( object.matrix ) === false ) { + if ( object.matrixAutoUpdate ) { - nodeDef.matrix = object.matrix.elements; + object.updateMatrix(); - } + } - } // We don't export empty strings name because it represents no-name in Three.js. + if ( isIdentityMatrix( object.matrix ) === false ) { + nodeDef.matrix = object.matrix.elements; - if ( object.name !== '' ) nodeDef.name = String( object.name ); - this.serializeUserData( object, nodeDef ); + } - if ( object.isMesh || object.isLine || object.isPoints ) { + } // We don't export empty strings name because it represents no-name in Three.js. - var meshIndex = this.processMesh( object ); - if ( meshIndex !== null ) nodeDef.mesh = meshIndex; - } else if ( object.isCamera ) { + if ( object.name !== '' ) nodeDef.name = String( object.name ); + this.serializeUserData( object, nodeDef ); - nodeDef.camera = this.processCamera( object ); + if ( object.isMesh || object.isLine || object.isPoints ) { - } + const meshIndex = this.processMesh( object ); + if ( meshIndex !== null ) nodeDef.mesh = meshIndex; - if ( object.isSkinnedMesh ) this.skins.push( object ); + } else if ( object.isCamera ) { - if ( object.children.length > 0 ) { + nodeDef.camera = this.processCamera( object ); - var children = []; + } - for ( var i = 0, l = object.children.length; i < l; i ++ ) { + if ( object.isSkinnedMesh ) this.skins.push( object ); - var child = object.children[ i ]; + if ( object.children.length > 0 ) { - if ( child.visible || options.onlyVisible === false ) { + const children = []; - var nodeIndex = this.processNode( child ); - if ( nodeIndex !== null ) children.push( nodeIndex ); + for ( let i = 0, l = object.children.length; i < l; i ++ ) { - } + const child = object.children[ i ]; - } + if ( child.visible || options.onlyVisible === false ) { - if ( children.length > 0 ) nodeDef.children = children; + const nodeIndex = this.processNode( child ); + if ( nodeIndex !== null ) children.push( nodeIndex ); + + } } - this._invokeAll( function ( ext ) { + if ( children.length > 0 ) nodeDef.children = children; - ext.writeNode && ext.writeNode( object, nodeDef ); + } - } ); + this._invokeAll( function ( ext ) { - var nodeIndex = json.nodes.push( nodeDef ) - 1; - nodeMap.set( object, nodeIndex ); - return nodeIndex; + ext.writeNode && ext.writeNode( object, nodeDef ); - }, + } ); - /** - * Process THREE.Scene - * @param {Scene} node THREE.Scene to process - */ - processScene: function ( scene ) { + const nodeIndex = json.nodes.push( nodeDef ) - 1; + nodeMap.set( object, nodeIndex ); + return nodeIndex; - var json = this.json; - var options = this.options; + } + /** + * Process THREE.Scene + * @param {Scene} node THREE.Scene to process + */ - if ( ! json.scenes ) { - json.scenes = []; - json.scene = 0; + processScene( scene ) { - } + const json = this.json; + const options = this.options; - var sceneDef = {}; - if ( scene.name !== '' ) sceneDef.name = scene.name; - json.scenes.push( sceneDef ); - var nodes = []; + if ( ! json.scenes ) { - for ( var i = 0, l = scene.children.length; i < l; i ++ ) { + json.scenes = []; + json.scene = 0; - var child = scene.children[ i ]; + } - if ( child.visible || options.onlyVisible === false ) { + const sceneDef = {}; + if ( scene.name !== '' ) sceneDef.name = scene.name; + json.scenes.push( sceneDef ); + const nodes = []; - var nodeIndex = this.processNode( child ); - if ( nodeIndex !== null ) nodes.push( nodeIndex ); + for ( let i = 0, l = scene.children.length; i < l; i ++ ) { - } + const child = scene.children[ i ]; + + if ( child.visible || options.onlyVisible === false ) { + + const nodeIndex = this.processNode( child ); + if ( nodeIndex !== null ) nodes.push( nodeIndex ); } - if ( nodes.length > 0 ) sceneDef.nodes = nodes; - this.serializeUserData( scene, sceneDef ); + } + + if ( nodes.length > 0 ) sceneDef.nodes = nodes; + this.serializeUserData( scene, sceneDef ); - }, + } + /** + * Creates a THREE.Scene to hold a list of objects and parse it + * @param {Array} objects List of objects to process + */ - /** - * Creates a THREE.Scene to hold a list of objects and parse it - * @param {Array} objects List of objects to process - */ - processObjects: function ( objects ) { - var scene = new THREE.Scene(); - scene.name = 'AuxScene'; + processObjects( objects ) { - for ( var i = 0; i < objects.length; i ++ ) { + const scene = new THREE.Scene(); + scene.name = 'AuxScene'; - // We push directly to children instead of calling `add` to prevent - // modify the .parent and break its original scene and hierarchy - scene.children.push( objects[ i ] ); + for ( let i = 0; i < objects.length; i ++ ) { - } + // We push directly to children instead of calling `add` to prevent + // modify the .parent and break its original scene and hierarchy + scene.children.push( objects[ i ] ); - this.processScene( scene ); + } - }, + this.processScene( scene ); - /** - * @param {THREE.Object3D|Array} input - */ - processInput: function ( input ) { + } + /** + * @param {THREE.Object3D|Array} input + */ - var options = this.options; - input = input instanceof Array ? input : [ input ]; - this._invokeAll( function ( ext ) { + processInput( input ) { - ext.beforeParse && ext.beforeParse( input ); + const options = this.options; + input = input instanceof Array ? input : [ input ]; - } ); + this._invokeAll( function ( ext ) { - var objectsWithoutScene = []; + ext.beforeParse && ext.beforeParse( input ); - for ( var i = 0; i < input.length; i ++ ) { + } ); - if ( input[ i ] instanceof THREE.Scene ) { + const objectsWithoutScene = []; - this.processScene( input[ i ] ); + for ( let i = 0; i < input.length; i ++ ) { - } else { + if ( input[ i ] instanceof THREE.Scene ) { - objectsWithoutScene.push( input[ i ] ); + this.processScene( input[ i ] ); - } + } else { + + objectsWithoutScene.push( input[ i ] ); } - if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); + } + + if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); - for ( var i = 0; i < this.skins.length; ++ i ) { + for ( let i = 0; i < this.skins.length; ++ i ) { - this.processSkin( this.skins[ i ] ); + this.processSkin( this.skins[ i ] ); - } + } - for ( var i = 0; i < options.animations.length; ++ i ) { + for ( let i = 0; i < options.animations.length; ++ i ) { - this.processAnimation( options.animations[ i ], input[ 0 ] ); + this.processAnimation( options.animations[ i ], input[ 0 ] ); - } + } - this._invokeAll( function ( ext ) { + this._invokeAll( function ( ext ) { - ext.afterParse && ext.afterParse( input ); + ext.afterParse && ext.afterParse( input ); - } ); + } ); - }, - _invokeAll: function ( func ) { + } - for ( var i = 0, il = this.plugins.length; i < il; i ++ ) { + _invokeAll( func ) { - func( this.plugins[ i ] ); + for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { - } + func( this.plugins[ i ] ); } - }; - /** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ - function GLTFLightExtension( writer ) { + } + + } + /** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ + + + class GLTFLightExtension { + + constructor( writer ) { this.writer = writer; this.name = 'KHR_lights_punctual'; } - GLTFLightExtension.prototype = { - constructor: GLTFLightExtension, - writeNode: function ( light, nodeDef ) { - - if ( ! light.isLight ) return; + writeNode( light, nodeDef ) { - if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { + if ( ! light.isLight ) return; - console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); - return; + if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { - } - - var writer = this.writer; - var json = writer.json; - var extensionsUsed = writer.extensionsUsed; - var lightDef = {}; - if ( light.name ) lightDef.name = light.name; - lightDef.color = light.color.toArray(); - lightDef.intensity = light.intensity; + console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); + return; - if ( light.isDirectionalLight ) { + } - lightDef.type = 'directional'; + const writer = this.writer; + const json = writer.json; + const extensionsUsed = writer.extensionsUsed; + const lightDef = {}; + if ( light.name ) lightDef.name = light.name; + lightDef.color = light.color.toArray(); + lightDef.intensity = light.intensity; - } else if ( light.isPointLight ) { + if ( light.isDirectionalLight ) { - lightDef.type = 'point'; - if ( light.distance > 0 ) lightDef.range = light.distance; + lightDef.type = 'directional'; - } else if ( light.isSpotLight ) { + } else if ( light.isPointLight ) { - lightDef.type = 'spot'; - if ( light.distance > 0 ) lightDef.range = light.distance; - lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; - lightDef.spot.outerConeAngle = light.angle; + lightDef.type = 'point'; + if ( light.distance > 0 ) lightDef.range = light.distance; - } + } else if ( light.isSpotLight ) { - if ( light.decay !== undefined && light.decay !== 2 ) { + lightDef.type = 'spot'; + if ( light.distance > 0 ) lightDef.range = light.distance; + lightDef.spot = {}; + lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; + lightDef.spot.outerConeAngle = light.angle; - console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + 'and expects light.decay=2.' ); + } - } + if ( light.decay !== undefined && light.decay !== 2 ) { - if ( light.target && ( light.target.parent !== light || light.target.position.x !== 0 || light.target.position.y !== 0 || light.target.position.z !== - 1 ) ) { + console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + 'and expects light.decay=2.' ); - console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + 'make light.target a child of the light with position 0,0,-1.' ); + } - } + if ( light.target && ( light.target.parent !== light || light.target.position.x !== 0 || light.target.position.y !== 0 || light.target.position.z !== - 1 ) ) { - if ( ! extensionsUsed[ this.name ] ) { + console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + 'make light.target a child of the light with position 0,0,-1.' ); - json.extensions = json.extensions || {}; - json.extensions[ this.name ] = { - lights: [] - }; - extensionsUsed[ this.name ] = true; + } - } + if ( ! extensionsUsed[ this.name ] ) { - var lights = json.extensions[ this.name ].lights; - lights.push( lightDef ); - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { - light: lights.length - 1 + json.extensions = json.extensions || {}; + json.extensions[ this.name ] = { + lights: [] }; + extensionsUsed[ this.name ] = true; } - }; - /** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ - function GLTFMaterialsUnlitExtension( writer ) { + const lights = json.extensions[ this.name ].lights; + lights.push( lightDef ); + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { + light: lights.length - 1 + }; + + } + + } + /** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ + + + class GLTFMaterialsUnlitExtension { + + constructor( writer ) { this.writer = writer; this.name = 'KHR_materials_unlit'; } - GLTFMaterialsUnlitExtension.prototype = { - constructor: GLTFMaterialsUnlitExtension, - writeMaterial: function ( material, materialDef ) { + writeMaterial( material, materialDef ) { - if ( ! material.isMeshBasicMaterial ) return; - var writer = this.writer; - var extensionsUsed = writer.extensionsUsed; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = {}; - extensionsUsed[ this.name ] = true; - materialDef.pbrMetallicRoughness.metallicFactor = 0.0; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; + if ( ! material.isMeshBasicMaterial ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = {}; + extensionsUsed[ this.name ] = true; + materialDef.pbrMetallicRoughness.metallicFactor = 0.0; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; - } - }; - /** - * Specular-Glossiness Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness - */ + } - function GLTFMaterialsPBRSpecularGlossiness( writer ) { + } + /** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness + */ + + + class GLTFMaterialsPBRSpecularGlossiness { + + constructor( writer ) { this.writer = writer; this.name = 'KHR_materials_pbrSpecularGlossiness'; } - GLTFMaterialsPBRSpecularGlossiness.prototype = { - constructor: GLTFMaterialsPBRSpecularGlossiness, - writeMaterial: function ( material, materialDef ) { + writeMaterial( material, materialDef ) { - if ( ! material.isGLTFSpecularGlossinessMaterial ) return; - var writer = this.writer; - var extensionsUsed = writer.extensionsUsed; - var extensionDef = {}; + if ( ! material.isGLTFSpecularGlossinessMaterial ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - if ( materialDef.pbrMetallicRoughness.baseColorFactor ) { + if ( materialDef.pbrMetallicRoughness.baseColorFactor ) { - extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor; + extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor; - } + } - var specularFactor = [ 1, 1, 1 ]; - material.specular.toArray( specularFactor, 0 ); - extensionDef.specularFactor = specularFactor; - extensionDef.glossinessFactor = material.glossiness; + const specularFactor = [ 1, 1, 1 ]; + material.specular.toArray( specularFactor, 0 ); + extensionDef.specularFactor = specularFactor; + extensionDef.glossinessFactor = material.glossiness; - if ( materialDef.pbrMetallicRoughness.baseColorTexture ) { + if ( materialDef.pbrMetallicRoughness.baseColorTexture ) { - extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture; + extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture; - } + } - if ( material.specularMap ) { + if ( material.specularMap ) { - var specularMapDef = { - index: writer.processTexture( material.specularMap ) - }; - writer.applyTextureTransform( specularMapDef, material.specularMap ); - extensionDef.specularGlossinessTexture = specularMapDef; + const specularMapDef = { + index: writer.processTexture( material.specularMap ) + }; + writer.applyTextureTransform( specularMapDef, material.specularMap ); + extensionDef.specularGlossinessTexture = specularMapDef; - } + } - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - extensionsUsed[ this.name ] = true; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - } - }; - /** - * Static utility functions - */ + } - GLTFExporter.Utils = { - insertKeyframe: function ( track, time ) { + } + /** + * Static utility functions + */ - var tolerance = 0.001; // 1ms - var valueSize = track.getValueSize(); - var times = new track.TimeBufferType( track.times.length + 1 ); - var values = new track.ValueBufferType( track.values.length + valueSize ); - var interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - var index; + GLTFExporter.Utils = { + insertKeyframe: function ( track, time ) { - if ( track.times.length === 0 ) { + const tolerance = 0.001; // 1ms - times[ 0 ] = time; + const valueSize = track.getValueSize(); + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + let index; - for ( var i = 0; i < valueSize; i ++ ) { + if ( track.times.length === 0 ) { - values[ i ] = 0; + times[ 0 ] = time; - } + for ( let i = 0; i < valueSize; i ++ ) { - index = 0; + values[ i ] = 0; - } else if ( time < track.times[ 0 ] ) { + } - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - times[ 0 ] = time; - times.set( track.times, 1 ); - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); - index = 0; + index = 0; - } else if ( time > track.times[ track.times.length - 1 ] ) { + } else if ( time < track.times[ 0 ] ) { - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + times[ 0 ] = time; + times.set( track.times, 1 ); + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); + index = 0; - return track.times.length - 1; + } else if ( time > track.times[ track.times.length - 1 ] ) { - } + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); - index = times.length - 1; + return track.times.length - 1; - } else { + } - for ( var i = 0; i < track.times.length; i ++ ) { + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); + index = times.length - 1; - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + } else { - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + for ( let i = 0; i < track.times.length; i ++ ) { - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - index = i + 1; - break; + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - } + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + index = i + 1; + break; } } - track.times = times; - track.values = values; - return index; + } - }, - mergeMorphTargetTracks: function ( clip, root ) { + track.times = times; + track.values = values; + return index; - var tracks = []; - var mergedTracks = {}; - var sourceTracks = clip.tracks; + }, + mergeMorphTargetTracks: function ( clip, root ) { - for ( var i = 0; i < sourceTracks.length; ++ i ) { + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; - var sourceTrack = sourceTracks[ i ]; - var sourceTrackBinding = THREE.PropertyBinding.parseTrackName( sourceTrack.name ); - var sourceTrackNode = THREE.PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + for ( let i = 0; i < sourceTracks.length; ++ i ) { - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = THREE.PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = THREE.PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - } + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + } - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - } + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( THREE.InterpolateLinear ); + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); } - var targetCount = sourceTrackNode.morphTargetInfluences.length; - var targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( THREE.InterpolateLinear ); - if ( targetIndex === undefined ) { + } - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - } + if ( targetIndex === undefined ) { - var mergedTrack; // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + } - mergedTrack = sourceTrack.clone(); - var values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + let mergedTrack; // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. - for ( var j = 0; j < mergedTrack.times.length; j ++ ) { + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + mergedTrack = sourceTrack.clone(); + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - } // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); - continue; + } // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. - } - var sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); + continue; - for ( var j = 0; j < mergedTrack.times.length; j ++ ) { + } - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. - } // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); - for ( var j = 0; j < sourceTrack.times.length; j ++ ) { + } // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. - var keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - } + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - } + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - clip.tracks = tracks; - return clip; + } } - }; - return GLTFExporter; - }(); + clip.tracks = tracks; + return clip; + + } + }; THREE.GLTFExporter = GLTFExporter; diff --git a/examples/js/exporters/MMDExporter.js b/examples/js/exporters/MMDExporter.js index cb91fe55046101..a794491366e754 100644 --- a/examples/js/exporters/MMDExporter.js +++ b/examples/js/exporters/MMDExporter.js @@ -5,68 +5,8 @@ * - mmd-parser https://github.com/takahirox/mmd-parser */ - var MMDExporter = function () { + class MMDExporter { - // Unicode to Shift_JIS table - var u2sTable; - - function unicodeToShiftjis( str ) { - - if ( u2sTable === undefined ) { - - var encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef - - var table = encoder.s2uTable; - u2sTable = {}; - var keys = Object.keys( table ); - - for ( var i = 0, il = keys.length; i < il; i ++ ) { - - var key = keys[ i ]; - var value = table[ key ]; - key = parseInt( key ); - u2sTable[ value ] = key; - - } - - } - - var array = []; - - for ( var i = 0, il = str.length; i < il; i ++ ) { - - var code = str.charCodeAt( i ); - var value = u2sTable[ code ]; - - if ( value === undefined ) { - - throw 'cannot convert charcode 0x' + code.toString( 16 ); - - } else if ( value > 0xff ) { - - array.push( value >> 8 & 0xff ); - array.push( value & 0xff ); - - } else { - - array.push( value & 0xff ); - - } - - } - - return new Uint8Array( array ); - - } - - function getBindBones( skin ) { - - // any more efficient ways? - var poseSkin = skin.clone(); - poseSkin.pose(); - return poseSkin.skeleton.bones; - - } /* TODO: implement // mesh -> pmd this.parsePmd = function ( object ) { @@ -79,13 +19,17 @@ }; */ + /* TODO: implement + // animation + skeleton -> vmd + this.parseVmd = function ( object ) { + }; + */ + /* * skeleton -> vpd * Returns Shift_JIS encoded Uint8Array. Otherwise return strings. */ - - - this.parseVpd = function ( skin, outputShiftJis, useOriginalBones ) { + parseVpd( skin, outputShiftJis, useOriginalBones ) { if ( skin.isSkinnedMesh !== true ) { @@ -97,7 +41,7 @@ function toStringsFromNumber( num ) { if ( Math.abs( num ) < 1e-6 ) num = 0; - var a = num.toString(); + let a = num.toString(); if ( a.indexOf( '.' ) === - 1 ) { @@ -106,18 +50,18 @@ } a += '000000'; - var index = a.indexOf( '.' ); - var d = a.slice( 0, index ); - var p = a.slice( index + 1, index + 7 ); + const index = a.indexOf( '.' ); + const d = a.slice( 0, index ); + const p = a.slice( index + 1, index + 7 ); return d + '.' + p; } function toStringsFromArray( array ) { - var a = []; + const a = []; - for ( var i = 0, il = array.length; i < il; i ++ ) { + for ( let i = 0, il = array.length; i < il; i ++ ) { a.push( toStringsFromNumber( array[ i ] ) ); @@ -128,23 +72,23 @@ } skin.updateMatrixWorld( true ); - var bones = skin.skeleton.bones; - var bones2 = getBindBones( skin ); - var position = new THREE.Vector3(); - var quaternion = new THREE.Quaternion(); - var quaternion2 = new THREE.Quaternion(); - var matrix = new THREE.Matrix4(); - var array = []; + const bones = skin.skeleton.bones; + const bones2 = getBindBones( skin ); + const position = new THREE.Vector3(); + const quaternion = new THREE.Quaternion(); + const quaternion2 = new THREE.Quaternion(); + const matrix = new THREE.Matrix4(); + const array = []; array.push( 'Vocaloid Pose Data file' ); array.push( '' ); array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' ); array.push( bones.length + ';' ); array.push( '' ); - for ( var i = 0, il = bones.length; i < il; i ++ ) { + for ( let i = 0, il = bones.length; i < il; i ++ ) { - var bone = bones[ i ]; - var bone2 = bones2[ i ]; + const bone = bones[ i ]; + const bone2 = bones2[ i ]; /* * use the bone matrix saved before solving IK. * see CCDIKSolver for the detail. @@ -162,8 +106,8 @@ position.setFromMatrixPosition( matrix ); quaternion.setFromRotationMatrix( matrix ); - var pArray = position.sub( bone2.position ).toArray(); - var qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray(); // right to left + const pArray = position.sub( bone2.position ).toArray(); + const qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray(); // right to left pArray[ 2 ] = - pArray[ 2 ]; qArray[ 0 ] = - qArray[ 0 ]; @@ -177,17 +121,73 @@ } array.push( '' ); - var lines = array.join( '\n' ); + const lines = array.join( '\n' ); return outputShiftJis === true ? unicodeToShiftjis( lines ) : lines; - }; - /* TODO: implement - // animation + skeleton -> vmd - this.parseVmd = function ( object ) { - }; - */ + } + + } // Unicode to Shift_JIS table + + + let u2sTable; + + function unicodeToShiftjis( str ) { + + if ( u2sTable === undefined ) { + + const encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef + + const table = encoder.s2uTable; + u2sTable = {}; + const keys = Object.keys( table ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + let key = keys[ i ]; + const value = table[ key ]; + key = parseInt( key ); + u2sTable[ value ] = key; + + } + + } + + const array = []; + + for ( let i = 0, il = str.length; i < il; i ++ ) { + + const code = str.charCodeAt( i ); + const value = u2sTable[ code ]; + + if ( value === undefined ) { + + throw 'cannot convert charcode 0x' + code.toString( 16 ); + + } else if ( value > 0xff ) { + + array.push( value >> 8 & 0xff ); + array.push( value & 0xff ); + + } else { + + array.push( value & 0xff ); + + } + + } + + return new Uint8Array( array ); + + } + + function getBindBones( skin ) { + + // any more efficient ways? + const poseSkin = skin.clone(); + poseSkin.pose(); + return poseSkin.skeleton.bones; - }; + } THREE.MMDExporter = MMDExporter; diff --git a/examples/js/exporters/OBJExporter.js b/examples/js/exporters/OBJExporter.js index aeda3c336f1459..bb9e4490d75284 100644 --- a/examples/js/exporters/OBJExporter.js +++ b/examples/js/exporters/OBJExporter.js @@ -1,33 +1,26 @@ ( function () { - var OBJExporter = function () {}; - - OBJExporter.prototype = { - constructor: OBJExporter, - parse: function ( object ) { - - var output = ''; - var indexVertex = 0; - var indexVertexUvs = 0; - var indexNormals = 0; - var vertex = new THREE.Vector3(); - var color = new THREE.Color(); - var normal = new THREE.Vector3(); - var uv = new THREE.Vector2(); - var i, - j, - k, - l, - m, - face = []; - - var parseMesh = function ( mesh ) { - - var nbVertex = 0; - var nbNormals = 0; - var nbVertexUvs = 0; - var geometry = mesh.geometry; - var normalMatrixWorld = new THREE.Matrix3(); + class OBJExporter { + + parse( object ) { + + let output = ''; + let indexVertex = 0; + let indexVertexUvs = 0; + let indexNormals = 0; + const vertex = new THREE.Vector3(); + const color = new THREE.Color(); + const normal = new THREE.Vector3(); + const uv = new THREE.Vector2(); + const face = []; + + function parseMesh( mesh ) { + + let nbVertex = 0; + let nbNormals = 0; + let nbVertexUvs = 0; + const geometry = mesh.geometry; + const normalMatrixWorld = new THREE.Matrix3(); if ( geometry.isBufferGeometry !== true ) { @@ -36,10 +29,10 @@ } // shortcuts - var vertices = geometry.getAttribute( 'position' ); - var normals = geometry.getAttribute( 'normal' ); - var uvs = geometry.getAttribute( 'uv' ); - var indices = geometry.getIndex(); // name of the mesh object + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const indices = geometry.getIndex(); // name of the mesh object output += 'o ' + mesh.name + '\n'; // name of the mesh material @@ -52,7 +45,7 @@ if ( vertices !== undefined ) { - for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { vertex.x = vertices.getX( i ); vertex.y = vertices.getY( i ); @@ -69,7 +62,7 @@ if ( uvs !== undefined ) { - for ( i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) { + for ( let i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) { uv.x = uvs.getX( i ); uv.y = uvs.getY( i ); // transform the uv to export format @@ -85,7 +78,7 @@ normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); - for ( i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) { + for ( let i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) { normal.x = normals.getX( i ); normal.y = normals.getY( i ); @@ -102,11 +95,11 @@ if ( indices !== null ) { - for ( i = 0, l = indices.count; i < l; i += 3 ) { + for ( let i = 0, l = indices.count; i < l; i += 3 ) { - for ( m = 0; m < 3; m ++ ) { + for ( let m = 0; m < 3; m ++ ) { - j = indices.getX( i + m ) + 1; + const j = indices.getX( i + m ) + 1; face[ m ] = indexVertex + j + ( normals || uvs ? '/' + ( uvs ? indexVertexUvs + j : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' ); } // transform the face to export format @@ -118,11 +111,11 @@ } else { - for ( i = 0, l = vertices.count; i < l; i += 3 ) { + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { - for ( m = 0; m < 3; m ++ ) { + for ( let m = 0; m < 3; m ++ ) { - j = i + m + 1; + const j = i + m + 1; face[ m ] = indexVertex + j + ( normals || uvs ? '/' + ( uvs ? indexVertexUvs + j : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' ); } // transform the face to export format @@ -139,13 +132,13 @@ indexVertexUvs += nbVertexUvs; indexNormals += nbNormals; - }; + } - var parseLine = function ( line ) { + function parseLine( line ) { - var nbVertex = 0; - var geometry = line.geometry; - var type = line.type; + let nbVertex = 0; + const geometry = line.geometry; + const type = line.type; if ( geometry.isBufferGeometry !== true ) { @@ -154,13 +147,13 @@ } // shortcuts - var vertices = geometry.getAttribute( 'position' ); // name of the line object + const vertices = geometry.getAttribute( 'position' ); // name of the line object output += 'o ' + line.name + '\n'; if ( vertices !== undefined ) { - for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { vertex.x = vertices.getX( i ); vertex.y = vertices.getY( i ); @@ -178,7 +171,7 @@ output += 'l '; - for ( j = 1, l = vertices.count; j <= l; j ++ ) { + for ( let j = 1, l = vertices.count; j <= l; j ++ ) { output += indexVertex + j + ' '; @@ -190,7 +183,7 @@ if ( type === 'LineSegments' ) { - for ( j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) { + for ( let j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) { output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n'; @@ -201,12 +194,12 @@ indexVertex += nbVertex; - }; + } - var parsePoints = function ( points ) { + function parsePoints( points ) { - var nbVertex = 0; - var geometry = points.geometry; + let nbVertex = 0; + const geometry = points.geometry; if ( geometry.isBufferGeometry !== true ) { @@ -214,13 +207,13 @@ } - var vertices = geometry.getAttribute( 'position' ); - var colors = geometry.getAttribute( 'color' ); + const vertices = geometry.getAttribute( 'position' ); + const colors = geometry.getAttribute( 'color' ); output += 'o ' + points.name + '\n'; if ( vertices !== undefined ) { - for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { vertex.fromBufferAttribute( vertices, i ); vertex.applyMatrix4( points.matrixWorld ); @@ -241,7 +234,7 @@ output += 'p '; - for ( j = 1, l = vertices.count; j <= l; j ++ ) { + for ( let j = 1, l = vertices.count; j <= l; j ++ ) { output += indexVertex + j + ' '; @@ -251,7 +244,7 @@ indexVertex += nbVertex; - }; + } object.traverse( function ( child ) { @@ -277,7 +270,8 @@ return output; } - }; + + } THREE.OBJExporter = OBJExporter; diff --git a/examples/js/exporters/PLYExporter.js b/examples/js/exporters/PLYExporter.js index f6bf7a63236989..30956bdb084df7 100644 --- a/examples/js/exporters/PLYExporter.js +++ b/examples/js/exporters/PLYExporter.js @@ -4,7 +4,7 @@ * https://github.com/gkjohnson/ply-exporter-js * * Usage: - * var exporter = new PLYExporter(); + * const exporter = new PLYExporter(); * * // second argument is a list of options * exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true }); @@ -13,11 +13,9 @@ * http://paulbourke.net/dataformats/ply/ */ - var PLYExporter = function () {}; + class PLYExporter { - PLYExporter.prototype = { - constructor: PLYExporter, - parse: function ( object, onDone, options ) { + parse( object, onDone, options ) { if ( onDone && typeof onDone === 'object' ) { @@ -34,8 +32,8 @@ if ( child.isMesh === true ) { - var mesh = child; - var geometry = mesh.geometry; + const mesh = child; + const geometry = mesh.geometry; if ( geometry.isBufferGeometry !== true ) { @@ -56,27 +54,27 @@ } // Default options - var defaultOptions = { + const defaultOptions = { binary: false, excludeAttributes: [], // normal, uv, color, index littleEndian: false }; options = Object.assign( defaultOptions, options ); - var excludeAttributes = options.excludeAttributes; - var includeNormals = false; - var includeColors = false; - var includeUVs = false; // count the vertices, check which properties are used, + const excludeAttributes = options.excludeAttributes; + let includeNormals = false; + let includeColors = false; + let includeUVs = false; // count the vertices, check which properties are used, // and cache the BufferGeometry - var vertexCount = 0; - var faceCount = 0; + let vertexCount = 0; + let faceCount = 0; object.traverse( function ( child ) { if ( child.isMesh === true ) { - var mesh = child; - var geometry = mesh.geometry; + const mesh = child; + const geometry = mesh.geometry; if ( geometry.isBufferGeometry !== true ) { @@ -84,11 +82,11 @@ } - var vertices = geometry.getAttribute( 'position' ); - var normals = geometry.getAttribute( 'normal' ); - var uvs = geometry.getAttribute( 'uv' ); - var colors = geometry.getAttribute( 'color' ); - var indices = geometry.getIndex(); + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); if ( vertices === undefined ) { @@ -105,7 +103,7 @@ } } ); - var includeIndices = excludeAttributes.indexOf( 'index' ) === - 1; + const includeIndices = excludeAttributes.indexOf( 'index' ) === - 1; includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1; includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1; includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1; @@ -120,8 +118,8 @@ } - var indexByteCount = 4; - var header = 'ply\n' + `format ${options.binary ? options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' : 'ascii'} 1.0\n` + `element vertex ${vertexCount}\n` + // position + const indexByteCount = 4; + let header = 'ply\n' + `format ${options.binary ? options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' : 'ascii'} 1.0\n` + `element vertex ${vertexCount}\n` + // position 'property float x\n' + 'property float y\n' + 'property float z\n'; if ( includeNormals === true ) { @@ -154,37 +152,37 @@ header += 'end_header\n'; // Generate attribute data - var vertex = new THREE.Vector3(); - var normalMatrixWorld = new THREE.Matrix3(); - var result = null; + const vertex = new THREE.Vector3(); + const normalMatrixWorld = new THREE.Matrix3(); + let result = null; if ( options.binary === true ) { // Binary File Generation - var headerBin = new TextEncoder().encode( header ); // 3 position values at 4 bytes + const headerBin = new TextEncoder().encode( header ); // 3 position values at 4 bytes // 3 normal values at 4 bytes // 3 color channels with 1 byte // 2 uv values at 4 bytes - var vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); // 1 byte shape desciptor + const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); // 1 byte shape desciptor // 3 vertex indices at ${indexByteCount} bytes - var faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0; - var output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) ); + const faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0; + const output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) ); new Uint8Array( output.buffer ).set( headerBin, 0 ); - var vOffset = headerBin.length; - var fOffset = headerBin.length + vertexListLength; - var writtenVertices = 0; + let vOffset = headerBin.length; + let fOffset = headerBin.length + vertexListLength; + let writtenVertices = 0; traverseMeshes( function ( mesh, geometry ) { - var vertices = geometry.getAttribute( 'position' ); - var normals = geometry.getAttribute( 'normal' ); - var uvs = geometry.getAttribute( 'uv' ); - var colors = geometry.getAttribute( 'color' ); - var indices = geometry.getIndex(); + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); - for ( var i = 0, l = vertices.count; i < l; i ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++ ) { vertex.x = vertices.getX( i ); vertex.y = vertices.getY( i ); @@ -279,7 +277,7 @@ // Create the face list if ( indices !== null ) { - for ( var i = 0, l = indices.count; i < l; i += 3 ) { + for ( let i = 0, l = indices.count; i < l; i += 3 ) { output.setUint8( fOffset, 3 ); fOffset += 1; @@ -294,7 +292,7 @@ } else { - for ( var i = 0, l = vertices.count; i < l; i += 3 ) { + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { output.setUint8( fOffset, 3 ); fOffset += 1; @@ -322,26 +320,26 @@ // Ascii File Generation // count the number of vertices - var writtenVertices = 0; - var vertexList = ''; - var faceList = ''; + let writtenVertices = 0; + let vertexList = ''; + let faceList = ''; traverseMeshes( function ( mesh, geometry ) { - var vertices = geometry.getAttribute( 'position' ); - var normals = geometry.getAttribute( 'normal' ); - var uvs = geometry.getAttribute( 'uv' ); - var colors = geometry.getAttribute( 'color' ); - var indices = geometry.getIndex(); + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); // form each line - for ( var i = 0, l = vertices.count; i < l; i ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++ ) { vertex.x = vertices.getX( i ); vertex.y = vertices.getY( i ); vertex.z = vertices.getZ( i ); vertex.applyMatrix4( mesh.matrixWorld ); // Position information - var line = vertex.x + ' ' + vertex.y + ' ' + vertex.z; // Normal information + let line = vertex.x + ' ' + vertex.y + ' ' + vertex.z; // Normal information if ( includeNormals === true ) { @@ -400,7 +398,7 @@ if ( indices !== null ) { - for ( var i = 0, l = indices.count; i < l; i += 3 ) { + for ( let i = 0, l = indices.count; i < l; i += 3 ) { faceList += `3 ${indices.getX( i + 0 ) + writtenVertices}`; faceList += ` ${indices.getX( i + 1 ) + writtenVertices}`; @@ -410,7 +408,7 @@ } else { - for ( var i = 0, l = vertices.count; i < l; i += 3 ) { + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { faceList += `3 ${writtenVertices + i} ${writtenVertices + i + 1} ${writtenVertices + i + 2}\n`; @@ -433,7 +431,8 @@ return result; } - }; + + } THREE.PLYExporter = PLYExporter; diff --git a/examples/js/exporters/STLExporter.js b/examples/js/exporters/STLExporter.js index 57f1253cf5d15e..f0d1162a1ce231 100644 --- a/examples/js/exporters/STLExporter.js +++ b/examples/js/exporters/STLExporter.js @@ -2,29 +2,26 @@ /** * Usage: - * var exporter = new STLExporter(); + * const exporter = new STLExporter(); * * // second argument is a list of options - * var data = exporter.parse( mesh, { binary: true } ); + * const data = exporter.parse( mesh, { binary: true } ); * */ - var STLExporter = function () {}; + class STLExporter { - STLExporter.prototype = { - constructor: STLExporter, - parse: function ( scene, options ) { + parse( scene, options = {} ) { - if ( options === undefined ) options = {}; - var binary = options.binary !== undefined ? options.binary : false; // + const binary = options.binary !== undefined ? options.binary : false; // - var objects = []; - var triangles = 0; + const objects = []; + let triangles = 0; scene.traverse( function ( object ) { if ( object.isMesh ) { - var geometry = object.geometry; + const geometry = object.geometry; if ( geometry.isBufferGeometry !== true ) { @@ -32,8 +29,8 @@ } - var index = geometry.index; - var positionAttribute = geometry.getAttribute( 'position' ); + const index = geometry.index; + const positionAttribute = geometry.getAttribute( 'position' ); triangles += index !== null ? index.count / 3 : positionAttribute.count / 3; objects.push( { object3d: object, @@ -43,13 +40,13 @@ } } ); - var output; - var offset = 80; // skip header + let output; + let offset = 80; // skip header if ( binary === true ) { - var bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4; - var arrayBuffer = new ArrayBuffer( bufferLength ); + const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4; + const arrayBuffer = new ArrayBuffer( bufferLength ); output = new DataView( arrayBuffer ); output.setUint32( offset, triangles, true ); offset += 4; @@ -61,28 +58,28 @@ } - var vA = new THREE.Vector3(); - var vB = new THREE.Vector3(); - var vC = new THREE.Vector3(); - var cb = new THREE.Vector3(); - var ab = new THREE.Vector3(); - var normal = new THREE.Vector3(); + const vA = new THREE.Vector3(); + const vB = new THREE.Vector3(); + const vC = new THREE.Vector3(); + const cb = new THREE.Vector3(); + const ab = new THREE.Vector3(); + const normal = new THREE.Vector3(); - for ( var i = 0, il = objects.length; i < il; i ++ ) { + for ( let i = 0, il = objects.length; i < il; i ++ ) { - var object = objects[ i ].object3d; - var geometry = objects[ i ].geometry; - var index = geometry.index; - var positionAttribute = geometry.getAttribute( 'position' ); + const object = objects[ i ].object3d; + const geometry = objects[ i ].geometry; + const index = geometry.index; + const positionAttribute = geometry.getAttribute( 'position' ); if ( index !== null ) { // indexed geometry - for ( var j = 0; j < index.count; j += 3 ) { + for ( let j = 0; j < index.count; j += 3 ) { - var a = index.getX( j + 0 ); - var b = index.getX( j + 1 ); - var c = index.getX( j + 2 ); + const a = index.getX( j + 0 ); + const b = index.getX( j + 1 ); + const c = index.getX( j + 2 ); writeFace( a, b, c, positionAttribute, object ); } @@ -90,11 +87,11 @@ } else { // non-indexed geometry - for ( var j = 0; j < positionAttribute.count; j += 3 ) { + for ( let j = 0; j < positionAttribute.count; j += 3 ) { - var a = j + 0; - var b = j + 1; - var c = j + 2; + const a = j + 0; + const b = j + 1; + const c = j + 2; writeFace( a, b, c, positionAttribute, object ); } @@ -192,7 +189,8 @@ } } - }; + + } THREE.STLExporter = STLExporter; diff --git a/examples/jsm/exporters/ColladaExporter.js b/examples/jsm/exporters/ColladaExporter.js index 7c49a3f3fbe907..a2fece77406ced 100644 --- a/examples/jsm/exporters/ColladaExporter.js +++ b/examples/jsm/exporters/ColladaExporter.js @@ -9,23 +9,17 @@ import { * https://github.com/gkjohnson/collada-exporter-js * * Usage: - * var exporter = new ColladaExporter(); + * const exporter = new ColladaExporter(); * - * var data = exporter.parse(mesh); + * const data = exporter.parse(mesh); * * Format Definition: * https://www.khronos.org/collada/ */ -var ColladaExporter = function () {}; +class ColladaExporter { -ColladaExporter.prototype = { - - constructor: ColladaExporter, - - parse: function ( object, onDone, options ) { - - options = options || {}; + parse( object, onDone, options = {} ) { options = Object.assign( { version: '1.4.1', @@ -41,7 +35,8 @@ ColladaExporter.prototype = { } - var version = options.version; + const version = options.version; + if ( version !== '1.4.1' && version !== '1.5.0' ) { console.warn( `ColladaExporter : Version ${ version } not supported for export. Only 1.4.1 and 1.5.0.` ); @@ -52,13 +47,14 @@ ColladaExporter.prototype = { // Convert the urdf xml into a well-formatted, indented format function format( urdf ) { - var IS_END_TAG = /^<\//; - var IS_SELF_CLOSING = /(\?>$)|(\/>$)/; - var HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/; + const IS_END_TAG = /^<\//; + const IS_SELF_CLOSING = /(\?>$)|(\/>$)/; + const HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/; + + const pad = ( ch, num ) => ( num > 0 ? ch + pad( ch, num - 1 ) : '' ); - var pad = ( ch, num ) => ( num > 0 ? ch + pad( ch, num - 1 ) : '' ); + let tagnum = 0; - var tagnum = 0; return urdf .match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g ) .map( tag => { @@ -69,7 +65,7 @@ ColladaExporter.prototype = { } - var res = `${ pad( ' ', tagnum ) }${ tag }`; + const res = `${ pad( ' ', tagnum ) }${ tag }`; if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) { @@ -87,10 +83,10 @@ ColladaExporter.prototype = { // Convert an image into a png format for saving function base64ToBuffer( str ) { - var b = atob( str ); - var buf = new Uint8Array( b.length ); + const b = atob( str ); + const buf = new Uint8Array( b.length ); - for ( var i = 0, l = buf.length; i < l; i ++ ) { + for ( let i = 0, l = buf.length; i < l; i ++ ) { buf[ i ] = b.charCodeAt( i ); @@ -100,7 +96,8 @@ ColladaExporter.prototype = { } - var canvas, ctx; + let canvas, ctx; + function imageToData( image, ext ) { canvas = canvas || document.createElement( 'canvas' ); @@ -112,7 +109,7 @@ ColladaExporter.prototype = { ctx.drawImage( image, 0, 0 ); // Get the base64 encoded data - var base64data = canvas + const base64data = canvas .toDataURL( `image/${ ext }`, 1 ) .replace( /^data:image\/(png|jpg);base64,/, '' ); @@ -122,17 +119,19 @@ ColladaExporter.prototype = { } // gets the attribute array. Generate a new array if the attribute is interleaved - var getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ]; + const getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ]; + function attrBufferToArray( attr ) { if ( attr.isInterleavedBufferAttribute ) { // use the typed array constructor to save on memory - var arr = new attr.array.constructor( attr.count * attr.itemSize ); - var size = attr.itemSize; - for ( var i = 0, l = attr.count; i < l; i ++ ) { + const arr = new attr.array.constructor( attr.count * attr.itemSize ); + const size = attr.itemSize; - for ( var j = 0; j < size; j ++ ) { + for ( let i = 0, l = attr.count; i < l; i ++ ) { + + for ( let j = 0; j < size; j ++ ) { arr[ i * size + j ] = attr[ getFuncs[ j ] ]( i ); @@ -162,8 +161,8 @@ ColladaExporter.prototype = { // Returns the string for a geometry's attribute function getAttribute( attr, name, params, type ) { - var array = attrBufferToArray( attr ); - var res = + const array = attrBufferToArray( attr ); + const res = `` + `` + @@ -184,7 +183,7 @@ ColladaExporter.prototype = { } // Returns the string for a node's transform information - var transMat; + let transMat; function getTransform( o ) { // ensure the object's matrix is up to date @@ -202,12 +201,12 @@ ColladaExporter.prototype = { // Returns the mesh id function processGeometry( g ) { - var info = geometryInfo.get( g ); + let info = geometryInfo.get( g ); if ( ! info ) { // convert the geometry to bufferGeometry if it isn't already - var bufferGeometry = g; + const bufferGeometry = g; if ( bufferGeometry.isBufferGeometry !== true ) { @@ -215,25 +214,25 @@ ColladaExporter.prototype = { } - var meshid = `Mesh${ libraryGeometries.length + 1 }`; + const meshid = `Mesh${ libraryGeometries.length + 1 }`; - var indexCount = + const indexCount = bufferGeometry.index ? bufferGeometry.index.count * bufferGeometry.index.itemSize : bufferGeometry.attributes.position.count; - var groups = + const groups = bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? bufferGeometry.groups : [ { start: 0, count: indexCount, materialIndex: 0 } ]; - var gname = g.name ? ` name="${ g.name }"` : ''; - var gnode = ``; + const gname = g.name ? ` name="${ g.name }"` : ''; + let gnode = ``; // define the geometry node and the vertices for the geometry - var posName = `${ meshid }-position`; - var vertName = `${ meshid }-vertices`; + const posName = `${ meshid }-position`; + const vertName = `${ meshid }-vertices`; gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' ); gnode += ``; @@ -243,10 +242,10 @@ ColladaExporter.prototype = { // MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/ // serialize normals - var triangleInputs = ``; + let triangleInputs = ``; if ( 'normal' in bufferGeometry.attributes ) { - var normName = `${ meshid }-normal`; + const normName = `${ meshid }-normal`; gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' ); triangleInputs += ``; @@ -255,7 +254,7 @@ ColladaExporter.prototype = { // serialize uvs if ( 'uv' in bufferGeometry.attributes ) { - var uvName = `${ meshid }-texcoord`; + const uvName = `${ meshid }-texcoord`; gnode += getAttribute( bufferGeometry.attributes.uv, uvName, [ 'S', 'T' ], 'float' ); triangleInputs += ``; @@ -264,7 +263,7 @@ ColladaExporter.prototype = { // serialize lightmap uvs if ( 'uv2' in bufferGeometry.attributes ) { - var uvName = `${ meshid }-texcoord2`; + const uvName = `${ meshid }-texcoord2`; gnode += getAttribute( bufferGeometry.attributes.uv2, uvName, [ 'S', 'T' ], 'float' ); triangleInputs += ``; @@ -273,13 +272,13 @@ ColladaExporter.prototype = { // serialize colors if ( 'color' in bufferGeometry.attributes ) { - var colName = `${ meshid }-color`; + const colName = `${ meshid }-color`; gnode += getAttribute( bufferGeometry.attributes.color, colName, [ 'X', 'Y', 'Z' ], 'uint8' ); triangleInputs += ``; } - var indexArray = null; + let indexArray = null; if ( bufferGeometry.index ) { indexArray = attrBufferToArray( bufferGeometry.index ); @@ -287,15 +286,15 @@ ColladaExporter.prototype = { } else { indexArray = new Array( indexCount ); - for ( var i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i; + for ( let i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i; } - for ( var i = 0, l = groups.length; i < l; i ++ ) { + for ( let i = 0, l = groups.length; i < l; i ++ ) { - var group = groups[ i ]; - var subarr = subArray( indexArray, group.start, group.count ); - var polycount = subarr.length / 3; + const group = groups[ i ]; + const subarr = subArray( indexArray, group.start, group.count ); + const polycount = subarr.length / 3; gnode += ``; gnode += triangleInputs; @@ -321,14 +320,14 @@ ColladaExporter.prototype = { // Returns the image library function processTexture( tex ) { - var texid = imageMap.get( tex ); + let texid = imageMap.get( tex ); if ( texid == null ) { texid = `image-${ libraryImages.length + 1 }`; - var ext = 'png'; - var name = tex.name || texid; - var imageNode = ``; + const ext = 'png'; + const name = tex.name || texid; + let imageNode = ``; if ( version === '1.5.0' ) { @@ -363,13 +362,13 @@ ColladaExporter.prototype = { // Returns the material id function processMaterial( m ) { - var matid = materialMap.get( m ); + let matid = materialMap.get( m ); if ( matid == null ) { matid = `Mat${ libraryEffects.length + 1 }`; - var type = 'phong'; + let type = 'phong'; if ( m.isMeshLambertMaterial === true ) { @@ -390,16 +389,16 @@ ColladaExporter.prototype = { } - var emissive = m.emissive ? m.emissive : new Color( 0, 0, 0 ); - var diffuse = m.color ? m.color : new Color( 0, 0, 0 ); - var specular = m.specular ? m.specular : new Color( 1, 1, 1 ); - var shininess = m.shininess || 0; - var reflectivity = m.reflectivity || 0; + const emissive = m.emissive ? m.emissive : new Color( 0, 0, 0 ); + const diffuse = m.color ? m.color : new Color( 0, 0, 0 ); + const specular = m.specular ? m.specular : new Color( 1, 1, 1 ); + const shininess = m.shininess || 0; + const reflectivity = m.reflectivity || 0; // Do not export and alpha map for the reasons mentioned in issue (#13792) // in three.js alpha maps are black and white, but collada expects the alpha // channel to specify the transparency - var transparencyNode = ''; + let transparencyNode = ''; if ( m.transparent === true ) { transparencyNode += @@ -419,7 +418,7 @@ ColladaExporter.prototype = { } - var techniqueNode = `<${ type }>` + + const techniqueNode = `<${ type }>` + '' + @@ -479,7 +478,7 @@ ColladaExporter.prototype = { ``; - var effectnode = + const effectnode = `` + '' + @@ -531,8 +530,8 @@ ColladaExporter.prototype = { ''; - var materialName = m.name ? ` name="${ m.name }"` : ''; - var materialNode = ``; + const materialName = m.name ? ` name="${ m.name }"` : ''; + const materialNode = ``; libraryMaterials.push( materialNode ); libraryEffects.push( effectnode ); @@ -547,7 +546,7 @@ ColladaExporter.prototype = { // Recursively process the object into a scene function processObject( o ) { - var node = ``; + let node = ``; node += getTransform( o ); @@ -555,19 +554,19 @@ ColladaExporter.prototype = { // function returns the id associated with the mesh and a "BufferGeometry" version // of the geometry in case it's not a geometry. - var geomInfo = processGeometry( o.geometry ); - var meshid = geomInfo.meshid; - var geometry = geomInfo.bufferGeometry; + const geomInfo = processGeometry( o.geometry ); + const meshid = geomInfo.meshid; + const geometry = geomInfo.bufferGeometry; // ids of the materials to bind to the geometry - var matids = null; - var matidsArray = []; + let matids = null; + let matidsArray; // get a list of materials to bind to the sub groups of the geometry. // If the amount of subgroups is greater than the materials, than reuse // the materials. - var mat = o.material || new MeshBasicMaterial(); - var materials = Array.isArray( mat ) ? mat : [ mat ]; + const mat = o.material || new MeshBasicMaterial(); + const materials = Array.isArray( mat ) ? mat : [ mat ]; if ( geometry.groups.length > materials.length ) { @@ -611,19 +610,19 @@ ColladaExporter.prototype = { } - var geometryInfo = new WeakMap(); - var materialMap = new WeakMap(); - var imageMap = new WeakMap(); - var textures = []; + const geometryInfo = new WeakMap(); + const materialMap = new WeakMap(); + const imageMap = new WeakMap(); + const textures = []; - var libraryImages = []; - var libraryGeometries = []; - var libraryEffects = []; - var libraryMaterials = []; - var libraryVisualScenes = processObject( object ); + const libraryImages = []; + const libraryGeometries = []; + const libraryEffects = []; + const libraryMaterials = []; + const libraryVisualScenes = processObject( object ); - var specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/'; - var dae = + const specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/'; + let dae = '' + `` + '' + @@ -652,7 +651,7 @@ ColladaExporter.prototype = { dae += ''; - var res = { + const res = { data: format( dae ), textures }; @@ -667,6 +666,7 @@ ColladaExporter.prototype = { } -}; +} + export { ColladaExporter }; diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index 38fc0d1542e4fb..0a36dc98585375 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -14,13 +14,17 @@ /* global DracoEncoderModule */ -var DRACOExporter = function () {}; - -DRACOExporter.prototype = { - - constructor: DRACOExporter, - - parse: function ( object, options ) { +class DRACOExporter { + + parse( object, options = { + decodeSpeed: 5, + encodeSpeed: 5, + encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, + quantization: [ 16, 8, 8, 8, 8 ], + exportUvs: true, + exportNormals: true, + exportColor: false, + } ) { if ( object.isBufferGeometry === true ) { @@ -34,28 +38,12 @@ DRACOExporter.prototype = { } - if ( options === undefined ) { - - options = { - - decodeSpeed: 5, - encodeSpeed: 5, - encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, - quantization: [ 16, 8, 8, 8, 8 ], - exportUvs: true, - exportNormals: true, - exportColor: false, - - }; - - } - - var geometry = object.geometry; + const geometry = object.geometry; - var dracoEncoder = DracoEncoderModule(); - var encoder = new dracoEncoder.Encoder(); - var builder; - var dracoObject; + const dracoEncoder = DracoEncoderModule(); + const encoder = new dracoEncoder.Encoder(); + let builder; + let dracoObject; if ( geometry.isBufferGeometry !== true ) { @@ -69,10 +57,10 @@ DRACOExporter.prototype = { builder = new dracoEncoder.MeshBuilder(); dracoObject = new dracoEncoder.Mesh(); - var vertices = geometry.getAttribute( 'position' ); + const vertices = geometry.getAttribute( 'position' ); builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); - var faces = geometry.getIndex(); + const faces = geometry.getIndex(); if ( faces !== null ) { @@ -80,9 +68,9 @@ DRACOExporter.prototype = { } else { - var faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); + const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); - for ( var i = 0; i < faces.length; i ++ ) { + for ( let i = 0; i < faces.length; i ++ ) { faces[ i ] = i; @@ -94,7 +82,7 @@ DRACOExporter.prototype = { if ( options.exportNormals === true ) { - var normals = geometry.getAttribute( 'normal' ); + const normals = geometry.getAttribute( 'normal' ); if ( normals !== undefined ) { @@ -106,7 +94,7 @@ DRACOExporter.prototype = { if ( options.exportUvs === true ) { - var uvs = geometry.getAttribute( 'uv' ); + const uvs = geometry.getAttribute( 'uv' ); if ( uvs !== undefined ) { @@ -118,7 +106,7 @@ DRACOExporter.prototype = { if ( options.exportColor === true ) { - var colors = geometry.getAttribute( 'color' ); + const colors = geometry.getAttribute( 'color' ); if ( colors !== undefined ) { @@ -133,12 +121,12 @@ DRACOExporter.prototype = { builder = new dracoEncoder.PointCloudBuilder(); dracoObject = new dracoEncoder.PointCloud(); - var vertices = geometry.getAttribute( 'position' ); + const vertices = geometry.getAttribute( 'position' ); builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); if ( options.exportColor === true ) { - var colors = geometry.getAttribute( 'color' ); + const colors = geometry.getAttribute( 'color' ); if ( colors !== undefined ) { @@ -156,12 +144,12 @@ DRACOExporter.prototype = { //Compress using draco encoder - var encodedData = new dracoEncoder.DracoInt8Array(); + const encodedData = new dracoEncoder.DracoInt8Array(); //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). - var encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; - var decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; + const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; + const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); @@ -177,7 +165,7 @@ DRACOExporter.prototype = { // The attribute values will be quantized in a box defined by the maximum extent of the attribute values. if ( options.quantization !== undefined ) { - for ( var i = 0; i < 5; i ++ ) { + for ( let i = 0; i < 5; i ++ ) { if ( options.quantization[ i ] !== undefined ) { @@ -189,7 +177,7 @@ DRACOExporter.prototype = { } - var length; + let length; if ( object.isMesh === true ) { @@ -210,9 +198,9 @@ DRACOExporter.prototype = { } //Copy encoded data to buffer. - var outputData = new Int8Array( new ArrayBuffer( length ) ); + const outputData = new Int8Array( new ArrayBuffer( length ) ); - for ( var i = 0; i < length; i ++ ) { + for ( let i = 0; i < length; i ++ ) { outputData[ i ] = encodedData.GetValue( i ); @@ -226,7 +214,7 @@ DRACOExporter.prototype = { } -}; +} // Encoder methods diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 7306422cfd23fb..2b9f2c13c21af4 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -21,9 +21,10 @@ import { Vector3 } from '../../../build/three.module.js'; -var GLTFExporter = ( function () { - function GLTFExporter() { +class GLTFExporter { + + constructor() { this.pluginCallbacks = []; @@ -47,285 +48,281 @@ var GLTFExporter = ( function () { } - GLTFExporter.prototype = { - - constructor: GLTFExporter, - - register: function ( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - - this.pluginCallbacks.push( callback ); - - } - - return this; - - }, - - unregister: function ( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - - } - - return this; + register( callback ) { - }, + if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - parse: function ( input, onDone, options ) { - - var writer = new GLTFWriter(); - var plugins = []; - - for ( var i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { - - plugins.push( this.pluginCallbacks[ i ]( writer ) ); - - } - - writer.setPlugins( plugins ); - writer.write( input, onDone, options ); + this.pluginCallbacks.push( callback ); } - }; + return this; - //------------------------------------------------------------------------------ - // Constants - //------------------------------------------------------------------------------ - - var WEBGL_CONSTANTS = { - POINTS: 0x0000, - LINES: 0x0001, - LINE_LOOP: 0x0002, - LINE_STRIP: 0x0003, - TRIANGLES: 0x0004, - TRIANGLE_STRIP: 0x0005, - TRIANGLE_FAN: 0x0006, - - UNSIGNED_BYTE: 0x1401, - UNSIGNED_SHORT: 0x1403, - FLOAT: 0x1406, - UNSIGNED_INT: 0x1405, - ARRAY_BUFFER: 0x8892, - ELEMENT_ARRAY_BUFFER: 0x8893, - - NEAREST: 0x2600, - LINEAR: 0x2601, - NEAREST_MIPMAP_NEAREST: 0x2700, - LINEAR_MIPMAP_NEAREST: 0x2701, - NEAREST_MIPMAP_LINEAR: 0x2702, - LINEAR_MIPMAP_LINEAR: 0x2703, - - CLAMP_TO_EDGE: 33071, - MIRRORED_REPEAT: 33648, - REPEAT: 10497 - }; - - var THREE_TO_WEBGL = {}; - - THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; - THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; - THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; - THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; - THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; - THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; + } - THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; - THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; - THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; + unregister( callback ) { - var PATH_PROPERTIES = { - scale: 'scale', - position: 'translation', - quaternion: 'rotation', - morphTargetInfluences: 'weights' - }; + if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - // GLB constants - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - var GLB_HEADER_BYTES = 12; - var GLB_HEADER_MAGIC = 0x46546C67; - var GLB_VERSION = 2; + } - var GLB_CHUNK_PREFIX_BYTES = 8; - var GLB_CHUNK_TYPE_JSON = 0x4E4F534A; - var GLB_CHUNK_TYPE_BIN = 0x004E4942; + return this; - //------------------------------------------------------------------------------ - // Utility functions - //------------------------------------------------------------------------------ + } /** - * Compare two arrays - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {Boolean} Returns true if both arrays are equal + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options */ - function equalArray( array1, array2 ) { - - return ( array1.length === array2.length ) && array1.every( function ( element, index ) { + parse( input, onDone, options ) { - return element === array2[ index ]; + const writer = new GLTFWriter(); + const plugins = []; - } ); - - } + for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { - /** - * Converts a string to an ArrayBuffer. - * @param {string} text - * @return {ArrayBuffer} - */ - function stringToArrayBuffer( text ) { + plugins.push( this.pluginCallbacks[ i ]( writer ) ); - if ( window.TextEncoder !== undefined ) { + } - return new TextEncoder().encode( text ).buffer; + writer.setPlugins( plugins ); + writer.write( input, onDone, options ); - } + } - var array = new Uint8Array( new ArrayBuffer( text.length ) ); +} + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const WEBGL_CONSTANTS = { + POINTS: 0x0000, + LINES: 0x0001, + LINE_LOOP: 0x0002, + LINE_STRIP: 0x0003, + TRIANGLES: 0x0004, + TRIANGLE_STRIP: 0x0005, + TRIANGLE_FAN: 0x0006, + + UNSIGNED_BYTE: 0x1401, + UNSIGNED_SHORT: 0x1403, + FLOAT: 0x1406, + UNSIGNED_INT: 0x1405, + ARRAY_BUFFER: 0x8892, + ELEMENT_ARRAY_BUFFER: 0x8893, + + NEAREST: 0x2600, + LINEAR: 0x2601, + NEAREST_MIPMAP_NEAREST: 0x2700, + LINEAR_MIPMAP_NEAREST: 0x2701, + NEAREST_MIPMAP_LINEAR: 0x2702, + LINEAR_MIPMAP_LINEAR: 0x2703, + + CLAMP_TO_EDGE: 33071, + MIRRORED_REPEAT: 33648, + REPEAT: 10497 +}; + +const THREE_TO_WEBGL = {}; + +THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; +THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; +THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; +THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; +THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; +THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; + +THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; +THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; +THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; + +const PATH_PROPERTIES = { + scale: 'scale', + position: 'translation', + quaternion: 'rotation', + morphTargetInfluences: 'weights' +}; + +// GLB constants +// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + +const GLB_HEADER_BYTES = 12; +const GLB_HEADER_MAGIC = 0x46546C67; +const GLB_VERSION = 2; + +const GLB_CHUNK_PREFIX_BYTES = 8; +const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; +const GLB_CHUNK_TYPE_BIN = 0x004E4942; + +//------------------------------------------------------------------------------ +// Utility functions +//------------------------------------------------------------------------------ + +/** + * Compare two arrays + * @param {Array} array1 Array 1 to compare + * @param {Array} array2 Array 2 to compare + * @return {Boolean} Returns true if both arrays are equal + */ +function equalArray( array1, array2 ) { + + return ( array1.length === array2.length ) && array1.every( function ( element, index ) { + + return element === array2[ index ]; + + } ); + +} + +/** + * Converts a string to an ArrayBuffer. + * @param {string} text + * @return {ArrayBuffer} + */ +function stringToArrayBuffer( text ) { + + if ( window.TextEncoder !== undefined ) { + + return new TextEncoder().encode( text ).buffer; - for ( var i = 0, il = text.length; i < il; i ++ ) { + } - var value = text.charCodeAt( i ); + const array = new Uint8Array( new ArrayBuffer( text.length ) ); - // Replacing multi-byte character with space(0x20). - array[ i ] = value > 0xFF ? 0x20 : value; + for ( let i = 0, il = text.length; i < il; i ++ ) { - } + const value = text.charCodeAt( i ); - return array.buffer; + // Replacing multi-byte character with space(0x20). + array[ i ] = value > 0xFF ? 0x20 : value; } - /** - * Is identity matrix - * - * @param {Matrix4} matrix - * @returns {Boolean} Returns true, if parameter is identity matrix - */ - function isIdentityMatrix( matrix ) { + return array.buffer; - return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); +} - } +/** + * Is identity matrix + * + * @param {Matrix4} matrix + * @returns {Boolean} Returns true, if parameter is identity matrix + */ +function isIdentityMatrix( matrix ) { - /** - * Get the min and max vectors from the given attribute - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {Integer} start - * @param {Integer} count - * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) - */ - function getMinMax( attribute, start, count ) { + return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); - var output = { +} - min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), - max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) +/** + * Get the min and max vectors from the given attribute + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {Integer} start + * @param {Integer} count + * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) + */ +function getMinMax( attribute, start, count ) { - }; + const output = { - for ( var i = start; i < start + count; i ++ ) { + min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), + max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) - for ( var a = 0; a < attribute.itemSize; a ++ ) { + }; - var value; + for ( let i = start; i < start + count; i ++ ) { - if ( attribute.itemSize > 4 ) { + for ( let a = 0; a < attribute.itemSize; a ++ ) { - // no support for interleaved data for itemSize > 4 + let value; - value = attribute.array[ i * attribute.itemSize + a ]; + if ( attribute.itemSize > 4 ) { - } else { + // no support for interleaved data for itemSize > 4 - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); + value = attribute.array[ i * attribute.itemSize + a ]; - } + } else { - output.min[ a ] = Math.min( output.min[ a ], value ); - output.max[ a ] = Math.max( output.max[ a ], value ); + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); } - } + output.min[ a ] = Math.min( output.min[ a ], value ); + output.max[ a ] = Math.max( output.max[ a ], value ); - return output; + } } - /** - * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - * - * @param {Integer} bufferSize The size the original buffer. - * @returns {Integer} new buffer size with required padding. - * - */ - function getPaddedBufferSize( bufferSize ) { + return output; - return Math.ceil( bufferSize / 4 ) * 4; +} - } +/** + * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + * + * @param {Integer} bufferSize The size the original buffer. + * @returns {Integer} new buffer size with required padding. + * + */ +function getPaddedBufferSize( bufferSize ) { - /** - * Returns a buffer aligned to 4-byte boundary. - * - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {Integer} paddingByte (Optional) - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer - */ - function getPaddedArrayBuffer( arrayBuffer, paddingByte ) { + return Math.ceil( bufferSize / 4 ) * 4; - paddingByte = paddingByte || 0; +} - var paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); +/** + * Returns a buffer aligned to 4-byte boundary. + * + * @param {ArrayBuffer} arrayBuffer Buffer to pad + * @param {Integer} paddingByte (Optional) + * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + */ +function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { - if ( paddedLength !== arrayBuffer.byteLength ) { + const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); - var array = new Uint8Array( paddedLength ); - array.set( new Uint8Array( arrayBuffer ) ); + if ( paddedLength !== arrayBuffer.byteLength ) { - if ( paddingByte !== 0 ) { + const array = new Uint8Array( paddedLength ); + array.set( new Uint8Array( arrayBuffer ) ); - for ( var i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { + if ( paddingByte !== 0 ) { - array[ i ] = paddingByte; + for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { - } + array[ i ] = paddingByte; } - return array.buffer; - } - return arrayBuffer; + return array.buffer; } - var cachedCanvas = null; + return arrayBuffer; - /** - * Writer - */ - function GLTFWriter() { +} + +let cachedCanvas = null; + +/** + * Writer + */ +class GLTFWriter { + + constructor() { this.plugins = []; @@ -360,2058 +357,2044 @@ var GLTFExporter = ( function () { } - GLTFWriter.prototype = { - - constructor: GLTFWriter, + setPlugins( plugins ) { - setPlugins: function ( plugins ) { + this.plugins = plugins; - this.plugins = plugins; - - }, + } - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - write: function ( input, onDone, options ) { + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options + */ + write( input, onDone, options ) { - this.options = Object.assign( {}, { - // default options - binary: false, - trs: false, - onlyVisible: true, - truncateDrawRange: true, - embedImages: true, - maxTextureSize: Infinity, - animations: [], - includeCustomExtensions: false - }, options ); + this.options = Object.assign( {}, { + // default options + binary: false, + trs: false, + onlyVisible: true, + truncateDrawRange: true, + embedImages: true, + maxTextureSize: Infinity, + animations: [], + includeCustomExtensions: false + }, options ); - if ( this.options.animations.length > 0 ) { + if ( this.options.animations.length > 0 ) { - // Only TRS properties, and not matrices, may be targeted by animation. - this.options.trs = true; + // Only TRS properties, and not matrices, may be targeted by animation. + this.options.trs = true; - } + } - this.processInput( input ); + this.processInput( input ); - var writer = this; + const writer = this; - Promise.all( this.pending ).then( function () { + Promise.all( this.pending ).then( function () { - var buffers = writer.buffers; - var json = writer.json; - var options = writer.options; - var extensionsUsed = writer.extensionsUsed; + const buffers = writer.buffers; + const json = writer.json; + const options = writer.options; + const extensionsUsed = writer.extensionsUsed; - // Merge buffers. - var blob = new Blob( buffers, { type: 'application/octet-stream' } ); + // Merge buffers. + const blob = new Blob( buffers, { type: 'application/octet-stream' } ); - // Declare extensions. - var extensionsUsedList = Object.keys( extensionsUsed ); + // Declare extensions. + const extensionsUsedList = Object.keys( extensionsUsed ); - if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; + if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; - // Update bytelength of the single buffer. - if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; + // Update bytelength of the single buffer. + if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; - if ( options.binary === true ) { + if ( options.binary === true ) { - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - var reader = new window.FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + const reader = new window.FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { - // Binary chunk. - var binaryChunk = getPaddedArrayBuffer( reader.result ); - var binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); - binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); - - // JSON chunk. - var jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); - var jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); - jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); - - // GLB header. - var header = new ArrayBuffer( GLB_HEADER_BYTES ); - var headerView = new DataView( header ); - headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); - headerView.setUint32( 4, GLB_VERSION, true ); - var totalByteLength = GLB_HEADER_BYTES - + jsonChunkPrefix.byteLength + jsonChunk.byteLength - + binaryChunkPrefix.byteLength + binaryChunk.byteLength; - headerView.setUint32( 8, totalByteLength, true ); - - var glbBlob = new Blob( [ - header, - jsonChunkPrefix, - jsonChunk, - binaryChunkPrefix, - binaryChunk - ], { type: 'application/octet-stream' } ); - - var glbReader = new window.FileReader(); - glbReader.readAsArrayBuffer( glbBlob ); - glbReader.onloadend = function () { - - onDone( glbReader.result ); - - }; + // Binary chunk. + const binaryChunk = getPaddedArrayBuffer( reader.result ); + const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); + binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); + + // JSON chunk. + const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); + const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); + jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); + jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); + + // GLB header. + const header = new ArrayBuffer( GLB_HEADER_BYTES ); + const headerView = new DataView( header ); + headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); + headerView.setUint32( 4, GLB_VERSION, true ); + const totalByteLength = GLB_HEADER_BYTES + + jsonChunkPrefix.byteLength + jsonChunk.byteLength + + binaryChunkPrefix.byteLength + binaryChunk.byteLength; + headerView.setUint32( 8, totalByteLength, true ); + + const glbBlob = new Blob( [ + header, + jsonChunkPrefix, + jsonChunk, + binaryChunkPrefix, + binaryChunk + ], { type: 'application/octet-stream' } ); + + const glbReader = new window.FileReader(); + glbReader.readAsArrayBuffer( glbBlob ); + glbReader.onloadend = function () { + + onDone( glbReader.result ); }; - } else { + }; - if ( json.buffers && json.buffers.length > 0 ) { + } else { - var reader = new window.FileReader(); - reader.readAsDataURL( blob ); - reader.onloadend = function () { + if ( json.buffers && json.buffers.length > 0 ) { - var base64data = reader.result; - json.buffers[ 0 ].uri = base64data; - onDone( json ); + const reader = new window.FileReader(); + reader.readAsDataURL( blob ); + reader.onloadend = function () { - }; + const base64data = reader.result; + json.buffers[ 0 ].uri = base64data; + onDone( json ); - } else { + }; - onDone( json ); + } else { - } + onDone( json ); } - } ); - - }, + } - /** - * Serializes a userData. - * - * @param {THREE.Object3D|THREE.Material} object - * @param {Object} objectDef - */ - serializeUserData: function ( object, objectDef ) { + } ); - if ( Object.keys( object.userData ).length === 0 ) return; + } - var options = this.options; - var extensionsUsed = this.extensionsUsed; + /** + * Serializes a userData. + * + * @param {THREE.Object3D|THREE.Material} object + * @param {Object} objectDef + */ + serializeUserData( object, objectDef ) { - try { + if ( Object.keys( object.userData ).length === 0 ) return; - var json = JSON.parse( JSON.stringify( object.userData ) ); + const options = this.options; + const extensionsUsed = this.extensionsUsed; - if ( options.includeCustomExtensions && json.gltfExtensions ) { + try { - if ( objectDef.extensions === undefined ) objectDef.extensions = {}; + const json = JSON.parse( JSON.stringify( object.userData ) ); - for ( var extensionName in json.gltfExtensions ) { + if ( options.includeCustomExtensions && json.gltfExtensions ) { - objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; - extensionsUsed[ extensionName ] = true; + if ( objectDef.extensions === undefined ) objectDef.extensions = {}; - } + for ( const extensionName in json.gltfExtensions ) { - delete json.gltfExtensions; + objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; + extensionsUsed[ extensionName ] = true; } - if ( Object.keys( json ).length > 0 ) objectDef.extras = json; + delete json.gltfExtensions; - } catch ( error ) { + } - console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + - 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + if ( Object.keys( json ).length > 0 ) objectDef.extras = json; - } + } catch ( error ) { - }, + console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); - /** - * Assign and return a temporal unique id for an object - * especially which doesn't have .uuid - * @param {Object} object - * @return {Integer} - */ - getUID: function ( object ) { + } - if ( ! this.uids.has( object ) ) this.uids.set( object, this.uid ++ ); + } - return this.uids.get( object ); + /** + * Assign and return a temporal unique id for an object + * especially which doesn't have .uuid + * @param {Object} object + * @return {Integer} + */ + getUID( object ) { - }, + if ( ! this.uids.has( object ) ) this.uids.set( object, this.uid ++ ); - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {Boolean} - */ - isNormalizedNormalAttribute: function ( normal ) { + return this.uids.get( object ); - var cache = this.cache; + } - if ( cache.attributesNormalized.has( normal ) ) return false; + /** + * Checks if normal attribute values are normalized. + * + * @param {BufferAttribute} normal + * @returns {Boolean} + */ + isNormalizedNormalAttribute( normal ) { - var v = new Vector3(); + const cache = this.cache; - for ( var i = 0, il = normal.count; i < il; i ++ ) { + if ( cache.attributesNormalized.has( normal ) ) return false; - // 0.0005 is from glTF-validator - if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; + const v = new Vector3(); - } + for ( let i = 0, il = normal.count; i < il; i ++ ) { - return true; + // 0.0005 is from glTF-validator + if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; - }, + } - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - createNormalizedNormalAttribute: function ( normal ) { + return true; - var cache = this.cache; + } - if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); + /** + * Creates normalized normal buffer attribute. + * + * @param {BufferAttribute} normal + * @returns {BufferAttribute} + * + */ + createNormalizedNormalAttribute( normal ) { - var attribute = normal.clone(); - var v = new Vector3(); + const cache = this.cache; - for ( var i = 0, il = attribute.count; i < il; i ++ ) { + if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); - v.fromBufferAttribute( attribute, i ); + const attribute = normal.clone(); + const v = new Vector3(); - if ( v.x === 0 && v.y === 0 && v.z === 0 ) { + for ( let i = 0, il = attribute.count; i < il; i ++ ) { - // if values can't be normalized set (1, 0, 0) - v.setX( 1.0 ); + v.fromBufferAttribute( attribute, i ); - } else { + if ( v.x === 0 && v.y === 0 && v.z === 0 ) { - v.normalize(); + // if values can't be normalized set (1, 0, 0) + v.setX( 1.0 ); - } + } else { - attribute.setXYZ( i, v.x, v.y, v.z ); + v.normalize(); } - cache.attributesNormalized.set( normal, attribute ); + attribute.setXYZ( i, v.x, v.y, v.z ); - return attribute; + } - }, + cache.attributesNormalized.set( normal, attribute ); - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - * - * @param {Object} mapDef - * @param {THREE.Texture} texture - */ - applyTextureTransform: function ( mapDef, texture ) { + return attribute; - var didTransform = false; - var transformDef = {}; + } - if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { + /** + * Applies a texture transform, if present, to the map definition. Requires + * the KHR_texture_transform extension. + * + * @param {Object} mapDef + * @param {THREE.Texture} texture + */ + applyTextureTransform( mapDef, texture ) { - transformDef.offset = texture.offset.toArray(); - didTransform = true; + let didTransform = false; + const transformDef = {}; - } + if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { - if ( texture.rotation !== 0 ) { + transformDef.offset = texture.offset.toArray(); + didTransform = true; - transformDef.rotation = texture.rotation; - didTransform = true; + } - } + if ( texture.rotation !== 0 ) { - if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { + transformDef.rotation = texture.rotation; + didTransform = true; - transformDef.scale = texture.repeat.toArray(); - didTransform = true; + } - } + if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { - if ( didTransform ) { + transformDef.scale = texture.repeat.toArray(); + didTransform = true; - mapDef.extensions = mapDef.extensions || {}; - mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; - this.extensionsUsed[ 'KHR_texture_transform' ] = true; + } - } + if ( didTransform ) { - }, + mapDef.extensions = mapDef.extensions || {}; + mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; + this.extensionsUsed[ 'KHR_texture_transform' ] = true; - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {Integer} - */ - processBuffer: function ( buffer ) { + } - var json = this.json; - var buffers = this.buffers; + } - if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; + /** + * Process a buffer to append to the default one. + * @param {ArrayBuffer} buffer + * @return {Integer} + */ + processBuffer( buffer ) { - // All buffers are merged before export. - buffers.push( buffer ); + const json = this.json; + const buffers = this.buffers; - return 0; + if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; - }, + // All buffers are merged before export. + buffers.push( buffer ); - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} target (Optional) Target usage of the BufferView - * @return {Object} - */ - processBufferView: function ( attribute, componentType, start, count, target ) { + return 0; - var json = this.json; + } - if ( ! json.bufferViews ) json.bufferViews = []; + /** + * Process and generate a BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} target (Optional) Target usage of the BufferView + * @return {Object} + */ + processBufferView( attribute, componentType, start, count, target ) { - // Create a new dataview and dump the attribute's array into it + const json = this.json; - var componentSize; + if ( ! json.bufferViews ) json.bufferViews = []; - if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + // Create a new dataview and dump the attribute's array into it - componentSize = 1; + let componentSize; - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - componentSize = 2; + componentSize = 1; - } else { + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - componentSize = 4; + componentSize = 2; - } + } else { - var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); - var dataView = new DataView( new ArrayBuffer( byteLength ) ); - var offset = 0; + componentSize = 4; - for ( var i = start; i < start + count; i ++ ) { + } - for ( var a = 0; a < attribute.itemSize; a ++ ) { + const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); + const dataView = new DataView( new ArrayBuffer( byteLength ) ); + let offset = 0; - var value; + for ( let i = start; i < start + count; i ++ ) { - if ( attribute.itemSize > 4 ) { + for ( let a = 0; a < attribute.itemSize; a ++ ) { - // no support for interleaved data for itemSize > 4 + let value; - value = attribute.array[ i * attribute.itemSize + a ]; + if ( attribute.itemSize > 4 ) { - } else { + // no support for interleaved data for itemSize > 4 - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); + value = attribute.array[ i * attribute.itemSize + a ]; - } + } else { - if ( componentType === WEBGL_CONSTANTS.FLOAT ) { + if ( a === 0 ) value = attribute.getX( i ); + else if ( a === 1 ) value = attribute.getY( i ); + else if ( a === 2 ) value = attribute.getZ( i ); + else if ( a === 3 ) value = attribute.getW( i ); - dataView.setFloat32( offset, value, true ); + } - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { + if ( componentType === WEBGL_CONSTANTS.FLOAT ) { - dataView.setUint32( offset, value, true ); + dataView.setFloat32( offset, value, true ); - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { - dataView.setUint16( offset, value, true ); + dataView.setUint32( offset, value, true ); - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - dataView.setUint8( offset, value ); + dataView.setUint16( offset, value, true ); - } + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - offset += componentSize; + dataView.setUint8( offset, value ); } + offset += componentSize; + } - var bufferViewDef = { + } - buffer: this.processBuffer( dataView.buffer ), - byteOffset: this.byteOffset, - byteLength: byteLength + const bufferViewDef = { - }; + buffer: this.processBuffer( dataView.buffer ), + byteOffset: this.byteOffset, + byteLength: byteLength - if ( target !== undefined ) bufferViewDef.target = target; - - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + }; - // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = attribute.itemSize * componentSize; + if ( target !== undefined ) bufferViewDef.target = target; - } + if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { - this.byteOffset += byteLength; + // Only define byteStride for vertex attributes. + bufferViewDef.byteStride = attribute.itemSize * componentSize; - json.bufferViews.push( bufferViewDef ); + } - // @TODO Merge bufferViews where possible. - var output = { + this.byteOffset += byteLength; - id: json.bufferViews.length - 1, - byteLength: 0 + json.bufferViews.push( bufferViewDef ); - }; + // @TODO Merge bufferViews where possible. + const output = { - return output; + id: json.bufferViews.length - 1, + byteLength: 0 - }, + }; - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise} - */ - processBufferViewImage: function ( blob ) { + return output; - var writer = this; - var json = writer.json; + } - if ( ! json.bufferViews ) json.bufferViews = []; + /** + * Process and generate a BufferView from an image Blob. + * @param {Blob} blob + * @return {Promise} + */ + processBufferViewImage( blob ) { - return new Promise( function ( resolve ) { + const writer = this; + const json = writer.json; - var reader = new window.FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + if ( ! json.bufferViews ) json.bufferViews = []; - var buffer = getPaddedArrayBuffer( reader.result ); + return new Promise( function ( resolve ) { - var bufferViewDef = { - buffer: writer.processBuffer( buffer ), - byteOffset: writer.byteOffset, - byteLength: buffer.byteLength - }; + const reader = new window.FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { - writer.byteOffset += buffer.byteLength; - resolve( json.bufferViews.push( bufferViewDef ) - 1 ); + const buffer = getPaddedArrayBuffer( reader.result ); + const bufferViewDef = { + buffer: writer.processBuffer( buffer ), + byteOffset: writer.byteOffset, + byteLength: buffer.byteLength }; - } ); + writer.byteOffset += buffer.byteLength; + resolve( json.bufferViews.push( bufferViewDef ) - 1 ); - }, + }; - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range - * @param {Integer} start (Optional) - * @param {Integer} count (Optional) - * @return {Integer|null} Index of the processed accessor on the "accessors" array - */ - processAccessor: function ( attribute, geometry, start, count ) { + } ); + + } - var options = this.options; - var json = this.json; + /** + * Process attribute to generate an accessor + * @param {BufferAttribute} attribute Attribute to process + * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range + * @param {Integer} start (Optional) + * @param {Integer} count (Optional) + * @return {Integer|null} Index of the processed accessor on the "accessors" array + */ + processAccessor( attribute, geometry, start, count ) { - var types = { + const options = this.options; + const json = this.json; - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 16: 'MAT4' + const types = { - }; + 1: 'SCALAR', + 2: 'VEC2', + 3: 'VEC3', + 4: 'VEC4', + 16: 'MAT4' - var componentType; + }; - // Detect the component type of the attribute array (float, uint or ushort) - if ( attribute.array.constructor === Float32Array ) { + let componentType; - componentType = WEBGL_CONSTANTS.FLOAT; + // Detect the component type of the attribute array (float, uint or ushort) + if ( attribute.array.constructor === Float32Array ) { - } else if ( attribute.array.constructor === Uint32Array ) { + componentType = WEBGL_CONSTANTS.FLOAT; - componentType = WEBGL_CONSTANTS.UNSIGNED_INT; + } else if ( attribute.array.constructor === Uint32Array ) { - } else if ( attribute.array.constructor === Uint16Array ) { + componentType = WEBGL_CONSTANTS.UNSIGNED_INT; - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; + } else if ( attribute.array.constructor === Uint16Array ) { - } else if ( attribute.array.constructor === Uint8Array ) { + componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; + } else if ( attribute.array.constructor === Uint8Array ) { - } else { + componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; - throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); + } else { - } + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); - if ( start === undefined ) start = 0; - if ( count === undefined ) count = attribute.count; + } - // @TODO Indexed buffer geometry with drawRange not supported yet - if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) { + if ( start === undefined ) start = 0; + if ( count === undefined ) count = attribute.count; - var end = start + count; - var end2 = geometry.drawRange.count === Infinity - ? attribute.count - : geometry.drawRange.start + geometry.drawRange.count; + // @TODO Indexed buffer geometry with drawRange not supported yet + if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) { - start = Math.max( start, geometry.drawRange.start ); - count = Math.min( end, end2 ) - start; + const end = start + count; + const end2 = geometry.drawRange.count === Infinity + ? attribute.count + : geometry.drawRange.start + geometry.drawRange.count; - if ( count < 0 ) count = 0; + start = Math.max( start, geometry.drawRange.start ); + count = Math.min( end, end2 ) - start; - } + if ( count < 0 ) count = 0; - // Skip creating an accessor if the attribute doesn't have data to export - if ( count === 0 ) return null; + } - var minMax = getMinMax( attribute, start, count ); - var bufferViewTarget; + // Skip creating an accessor if the attribute doesn't have data to export + if ( count === 0 ) return null; - // If geometry isn't provided, don't infer the target usage of the bufferView. For - // animation samplers, target must not be set. - if ( geometry !== undefined ) { + const minMax = getMinMax( attribute, start, count ); + let bufferViewTarget; - bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; + // If geometry isn't provided, don't infer the target usage of the bufferView. For + // animation samplers, target must not be set. + if ( geometry !== undefined ) { - } + bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; - var bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); + } - var accessorDef = { + const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[ attribute.itemSize ] + const accessorDef = { - }; + bufferView: bufferView.id, + byteOffset: bufferView.byteOffset, + componentType: componentType, + count: count, + max: minMax.max, + min: minMax.min, + type: types[ attribute.itemSize ] - if ( attribute.normalized === true ) accessorDef.normalized = true; - if ( ! json.accessors ) json.accessors = []; + }; - return json.accessors.push( accessorDef ) - 1; + if ( attribute.normalized === true ) accessorDef.normalized = true; + if ( ! json.accessors ) json.accessors = []; - }, + return json.accessors.push( accessorDef ) - 1; - /** - * Process image - * @param {Image} image to process - * @param {Integer} format of the image (e.g. RGBFormat, RGBAFormat etc) - * @param {Boolean} flipY before writing out the image - * @return {Integer} Index of the processed texture in the "images" array - */ - processImage: function ( image, format, flipY ) { + } - var writer = this; - var cache = writer.cache; - var json = writer.json; - var options = writer.options; - var pending = writer.pending; + /** + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (e.g. RGBFormat, RGBAFormat etc) + * @param {Boolean} flipY before writing out the image + * @return {Integer} Index of the processed texture in the "images" array + */ + processImage( image, format, flipY ) { - if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); + const writer = this; + const cache = writer.cache; + const json = writer.json; + const options = writer.options; + const pending = writer.pending; - var cachedImages = cache.images.get( image ); - var mimeType = format === RGBAFormat ? 'image/png' : 'image/jpeg'; - var key = mimeType + ':flipY/' + flipY.toString(); + if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); - if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; + const cachedImages = cache.images.get( image ); + const mimeType = format === RGBAFormat ? 'image/png' : 'image/jpeg'; + const key = mimeType + ':flipY/' + flipY.toString(); - if ( ! json.images ) json.images = []; + if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; - var imageDef = { mimeType: mimeType }; + if ( ! json.images ) json.images = []; - if ( options.embedImages ) { + const imageDef = { mimeType: mimeType }; - var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' ); + if ( options.embedImages ) { - canvas.width = Math.min( image.width, options.maxTextureSize ); - canvas.height = Math.min( image.height, options.maxTextureSize ); + const canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' ); - var ctx = canvas.getContext( '2d' ); + canvas.width = Math.min( image.width, options.maxTextureSize ); + canvas.height = Math.min( image.height, options.maxTextureSize ); - if ( flipY === true ) { + const ctx = canvas.getContext( '2d' ); - ctx.translate( 0, canvas.height ); - ctx.scale( 1, - 1 ); + if ( flipY === true ) { - } + ctx.translate( 0, canvas.height ); + ctx.scale( 1, - 1 ); - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + } - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - } else { + ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); - if ( format !== RGBAFormat && format !== RGBFormat ) { + } else { - console.error( 'GLTFExporter: Only RGB and RGBA formats are supported.' ); + if ( format !== RGBAFormat && format !== RGBFormat ) { - } + console.error( 'GLTFExporter: Only RGB and RGBA formats are supported.' ); - if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { + } - console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); + if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { - } + console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); - var data = image.data; + } - if ( format === RGBFormat ) { + let data = image.data; - data = new Uint8ClampedArray( image.height * image.width * 4 ); + if ( format === RGBFormat ) { - for ( var i = 0, j = 0; i < data.length; i += 4, j += 3 ) { + data = new Uint8ClampedArray( image.height * image.width * 4 ); - data[ i + 0 ] = image.data[ j + 0 ]; - data[ i + 1 ] = image.data[ j + 1 ]; - data[ i + 2 ] = image.data[ j + 2 ]; - data[ i + 3 ] = 255; + for ( let i = 0, j = 0; i < data.length; i += 4, j += 3 ) { - } + data[ i + 0 ] = image.data[ j + 0 ]; + data[ i + 1 ] = image.data[ j + 1 ]; + data[ i + 2 ] = image.data[ j + 2 ]; + data[ i + 3 ] = 255; } - ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); - } - if ( options.binary === true ) { - - pending.push( new Promise( function ( resolve ) { + ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); - canvas.toBlob( function ( blob ) { + } - writer.processBufferViewImage( blob ).then( function ( bufferViewIndex ) { + if ( options.binary === true ) { - imageDef.bufferView = bufferViewIndex; - resolve(); + pending.push( new Promise( function ( resolve ) { - } ); + canvas.toBlob( function ( blob ) { - }, mimeType ); + writer.processBufferViewImage( blob ).then( function ( bufferViewIndex ) { - } ) ); + imageDef.bufferView = bufferViewIndex; + resolve(); - } else { + } ); - imageDef.uri = canvas.toDataURL( mimeType ); + }, mimeType ); - } + } ) ); } else { - imageDef.uri = image.src; + imageDef.uri = canvas.toDataURL( mimeType ); } - var index = json.images.push( imageDef ) - 1; - cachedImages[ key ] = index; - return index; + } else { - }, + imageDef.uri = image.src; - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {Integer} Index of the processed texture in the "samplers" array - */ - processSampler: function ( map ) { - - var json = this.json; + } - if ( ! json.samplers ) json.samplers = []; + const index = json.images.push( imageDef ) - 1; + cachedImages[ key ] = index; + return index; - var samplerDef = { - magFilter: THREE_TO_WEBGL[ map.magFilter ], - minFilter: THREE_TO_WEBGL[ map.minFilter ], - wrapS: THREE_TO_WEBGL[ map.wrapS ], - wrapT: THREE_TO_WEBGL[ map.wrapT ] - }; + } - return json.samplers.push( samplerDef ) - 1; + /** + * Process sampler + * @param {Texture} map Texture to process + * @return {Integer} Index of the processed texture in the "samplers" array + */ + processSampler( map ) { - }, + const json = this.json; - /** - * Process texture - * @param {Texture} map Map to process - * @return {Integer} Index of the processed texture in the "textures" array - */ - processTexture: function ( map ) { + if ( ! json.samplers ) json.samplers = []; - var cache = this.cache; - var json = this.json; + const samplerDef = { + magFilter: THREE_TO_WEBGL[ map.magFilter ], + minFilter: THREE_TO_WEBGL[ map.minFilter ], + wrapS: THREE_TO_WEBGL[ map.wrapS ], + wrapT: THREE_TO_WEBGL[ map.wrapT ] + }; - if ( cache.textures.has( map ) ) return cache.textures.get( map ); + return json.samplers.push( samplerDef ) - 1; - if ( ! json.textures ) json.textures = []; + } - var textureDef = { - sampler: this.processSampler( map ), - source: this.processImage( map.image, map.format, map.flipY ) - }; + /** + * Process texture + * @param {Texture} map Map to process + * @return {Integer} Index of the processed texture in the "textures" array + */ + processTexture( map ) { - if ( map.name ) textureDef.name = map.name; + const cache = this.cache; + const json = this.json; - this._invokeAll( function ( ext ) { + if ( cache.textures.has( map ) ) return cache.textures.get( map ); - ext.writeTexture && ext.writeTexture( map, textureDef ); + if ( ! json.textures ) json.textures = []; - } ); + const textureDef = { + sampler: this.processSampler( map ), + source: this.processImage( map.image, map.format, map.flipY ) + }; - var index = json.textures.push( textureDef ) - 1; - cache.textures.set( map, index ); - return index; + if ( map.name ) textureDef.name = map.name; - }, + this._invokeAll( function ( ext ) { - /** - * Process material - * @param {THREE.Material} material Material to process - * @return {Integer|null} Index of the processed material in the "materials" array - */ - processMaterial: function ( material ) { + ext.writeTexture && ext.writeTexture( map, textureDef ); - var cache = this.cache; - var json = this.json; + } ); - if ( cache.materials.has( material ) ) return cache.materials.get( material ); + const index = json.textures.push( textureDef ) - 1; + cache.textures.set( map, index ); + return index; - if ( material.isShaderMaterial ) { + } - console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); - return null; + /** + * Process material + * @param {THREE.Material} material Material to process + * @return {Integer|null} Index of the processed material in the "materials" array + */ + processMaterial( material ) { - } + const cache = this.cache; + const json = this.json; - if ( ! json.materials ) json.materials = []; + if ( cache.materials.has( material ) ) return cache.materials.get( material ); - // @QUESTION Should we avoid including any attribute that has the default value? - var materialDef = { pbrMetallicRoughness: {} }; + if ( material.isShaderMaterial ) { - if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { + console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); + return null; - console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); + } - } + if ( ! json.materials ) json.materials = []; - // pbrMetallicRoughness.baseColorFactor - var color = material.color.toArray().concat( [ material.opacity ] ); + // @QUESTION Should we avoid including any attribute that has the default value? + const materialDef = { pbrMetallicRoughness: {} }; - if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { + if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { - materialDef.pbrMetallicRoughness.baseColorFactor = color; + console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); - } + } - if ( material.isMeshStandardMaterial ) { + // pbrMetallicRoughness.baseColorFactor + const color = material.color.toArray().concat( [ material.opacity ] ); - materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; - materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; + if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { - } else { + materialDef.pbrMetallicRoughness.baseColorFactor = color; - materialDef.pbrMetallicRoughness.metallicFactor = 0.5; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; + } - } + if ( material.isMeshStandardMaterial ) { - // pbrMetallicRoughness.metallicRoughnessTexture - if ( material.metalnessMap || material.roughnessMap ) { + materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; + materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; - if ( material.metalnessMap === material.roughnessMap ) { + } else { - var metalRoughMapDef = { index: this.processTexture( material.metalnessMap ) }; - this.applyTextureTransform( metalRoughMapDef, material.metalnessMap ); - materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + materialDef.pbrMetallicRoughness.metallicFactor = 0.5; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; - } else { + } - console.warn( 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' ); + // pbrMetallicRoughness.metallicRoughnessTexture + if ( material.metalnessMap || material.roughnessMap ) { - } + if ( material.metalnessMap === material.roughnessMap ) { - } + const metalRoughMapDef = { index: this.processTexture( material.metalnessMap ) }; + this.applyTextureTransform( metalRoughMapDef, material.metalnessMap ); + materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; - // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture - if ( material.map ) { + } else { - var baseColorMapDef = { index: this.processTexture( material.map ) }; - this.applyTextureTransform( baseColorMapDef, material.map ); - materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + console.warn( 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' ); } - if ( material.emissive ) { + } - // emissiveFactor - var emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray(); + // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture + if ( material.map ) { - if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) { + const baseColorMapDef = { index: this.processTexture( material.map ) }; + this.applyTextureTransform( baseColorMapDef, material.map ); + materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; - materialDef.emissiveFactor = emissive; + } - } + if ( material.emissive ) { - // emissiveTexture - if ( material.emissiveMap ) { + // emissiveFactor + const emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray(); - var emissiveMapDef = { index: this.processTexture( material.emissiveMap ) }; - this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); - materialDef.emissiveTexture = emissiveMapDef; + if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) { - } + materialDef.emissiveFactor = emissive; } - // normalTexture - if ( material.normalMap ) { + // emissiveTexture + if ( material.emissiveMap ) { - var normalMapDef = { index: this.processTexture( material.normalMap ) }; + const emissiveMapDef = { index: this.processTexture( material.emissiveMap ) }; + this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); + materialDef.emissiveTexture = emissiveMapDef; - if ( material.normalScale && material.normalScale.x !== - 1 ) { + } - if ( material.normalScale.x !== material.normalScale.y ) { + } - console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' ); + // normalTexture + if ( material.normalMap ) { - } + const normalMapDef = { index: this.processTexture( material.normalMap ) }; - normalMapDef.scale = material.normalScale.x; + if ( material.normalScale && material.normalScale.x !== - 1 ) { + + if ( material.normalScale.x !== material.normalScale.y ) { + + console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' ); } - this.applyTextureTransform( normalMapDef, material.normalMap ); - materialDef.normalTexture = normalMapDef; + normalMapDef.scale = material.normalScale.x; } - // occlusionTexture - if ( material.aoMap ) { + this.applyTextureTransform( normalMapDef, material.normalMap ); + materialDef.normalTexture = normalMapDef; - var occlusionMapDef = { - index: this.processTexture( material.aoMap ), - texCoord: 1 - }; + } - if ( material.aoMapIntensity !== 1.0 ) { + // occlusionTexture + if ( material.aoMap ) { - occlusionMapDef.strength = material.aoMapIntensity; + const occlusionMapDef = { + index: this.processTexture( material.aoMap ), + texCoord: 1 + }; - } + if ( material.aoMapIntensity !== 1.0 ) { - this.applyTextureTransform( occlusionMapDef, material.aoMap ); - materialDef.occlusionTexture = occlusionMapDef; + occlusionMapDef.strength = material.aoMapIntensity; } - // alphaMode - if ( material.transparent ) { + this.applyTextureTransform( occlusionMapDef, material.aoMap ); + materialDef.occlusionTexture = occlusionMapDef; + + } - materialDef.alphaMode = 'BLEND'; + // alphaMode + if ( material.transparent ) { - } else { + materialDef.alphaMode = 'BLEND'; - if ( material.alphaTest > 0.0 ) { + } else { - materialDef.alphaMode = 'MASK'; - materialDef.alphaCutoff = material.alphaTest; + if ( material.alphaTest > 0.0 ) { - } + materialDef.alphaMode = 'MASK'; + materialDef.alphaCutoff = material.alphaTest; } - // doubleSided - if ( material.side === DoubleSide ) materialDef.doubleSided = true; - if ( material.name !== '' ) materialDef.name = material.name; + } - this.serializeUserData( material, materialDef ); + // doubleSided + if ( material.side === DoubleSide ) materialDef.doubleSided = true; + if ( material.name !== '' ) materialDef.name = material.name; - this._invokeAll( function ( ext ) { + this.serializeUserData( material, materialDef ); - ext.writeMaterial && ext.writeMaterial( material, materialDef ); + this._invokeAll( function ( ext ) { - } ); + ext.writeMaterial && ext.writeMaterial( material, materialDef ); - var index = json.materials.push( materialDef ) - 1; - cache.materials.set( material, index ); - return index; + } ); - }, + const index = json.materials.push( materialDef ) - 1; + cache.materials.set( material, index ); + return index; - /** - * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Integer|null} Index of the processed mesh in the "meshes" array - */ - processMesh: function ( mesh ) { + } - var cache = this.cache; - var json = this.json; + /** + * Process mesh + * @param {THREE.Mesh} mesh Mesh to process + * @return {Integer|null} Index of the processed mesh in the "meshes" array + */ + processMesh( mesh ) { - var meshCacheKeyParts = [ mesh.geometry.uuid ]; + const cache = this.cache; + const json = this.json; - if ( Array.isArray( mesh.material ) ) { + const meshCacheKeyParts = [ mesh.geometry.uuid ]; - for ( var i = 0, l = mesh.material.length; i < l; i ++ ) { + if ( Array.isArray( mesh.material ) ) { - meshCacheKeyParts.push( mesh.material[ i ].uuid ); + for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { - } + meshCacheKeyParts.push( mesh.material[ i ].uuid ); - } else { + } - meshCacheKeyParts.push( mesh.material.uuid ); + } else { - } + meshCacheKeyParts.push( mesh.material.uuid ); - var meshCacheKey = meshCacheKeyParts.join( ':' ); + } - if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); + const meshCacheKey = meshCacheKeyParts.join( ':' ); - var geometry = mesh.geometry; - var mode; + if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); - // Use the correct mode - if ( mesh.isLineSegments ) { + const geometry = mesh.geometry; + let mode; - mode = WEBGL_CONSTANTS.LINES; + // Use the correct mode + if ( mesh.isLineSegments ) { - } else if ( mesh.isLineLoop ) { + mode = WEBGL_CONSTANTS.LINES; - mode = WEBGL_CONSTANTS.LINE_LOOP; + } else if ( mesh.isLineLoop ) { - } else if ( mesh.isLine ) { + mode = WEBGL_CONSTANTS.LINE_LOOP; - mode = WEBGL_CONSTANTS.LINE_STRIP; + } else if ( mesh.isLine ) { - } else if ( mesh.isPoints ) { + mode = WEBGL_CONSTANTS.LINE_STRIP; - mode = WEBGL_CONSTANTS.POINTS; + } else if ( mesh.isPoints ) { - } else { + mode = WEBGL_CONSTANTS.POINTS; - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; + } else { - } + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; - if ( geometry.isBufferGeometry !== true ) { + } - throw new Error( 'THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.' ); + if ( geometry.isBufferGeometry !== true ) { - } + throw new Error( 'THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.' ); - var meshDef = {}; - var attributes = {}; - var primitives = []; - var targets = []; - - // Conversion between attributes names in threejs and gltf spec - var nameConversion = { - uv: 'TEXCOORD_0', - uv2: 'TEXCOORD_1', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0' - }; + } - var originalNormal = geometry.getAttribute( 'normal' ); + const meshDef = {}; + const attributes = {}; + const primitives = []; + const targets = []; + + // Conversion between attributes names in threejs and gltf spec + const nameConversion = { + uv: 'TEXCOORD_0', + uv2: 'TEXCOORD_1', + color: 'COLOR_0', + skinWeight: 'WEIGHTS_0', + skinIndex: 'JOINTS_0' + }; - if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { + const originalNormal = geometry.getAttribute( 'normal' ); - console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); + if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { - geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); + console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); - } + geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); - // @QUESTION Detect if .vertexColors = true? - // For every attribute create an accessor - var modifiedAttribute = null; + } - for ( var attributeName in geometry.attributes ) { + // @QUESTION Detect if .vertexColors = true? + // For every attribute create an accessor + let modifiedAttribute = null; - // Ignore morph target attributes, which are exported later. - if ( attributeName.substr( 0, 5 ) === 'morph' ) continue; + for ( let attributeName in geometry.attributes ) { - var attribute = geometry.attributes[ attributeName ]; - attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); + // Ignore morph target attributes, which are exported later. + if ( attributeName.substr( 0, 5 ) === 'morph' ) continue; - // Prefix all geometry attributes except the ones specifically - // listed in the spec; non-spec attributes are considered custom. - var validVertexAttributes = - /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; + const attribute = geometry.attributes[ attributeName ]; + attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); - if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; + // Prefix all geometry attributes except the ones specifically + // listed in the spec; non-spec attributes are considered custom. + const validVertexAttributes = + /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; - if ( cache.attributes.has( this.getUID( attribute ) ) ) { + if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName; - attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; + if ( cache.attributes.has( this.getUID( attribute ) ) ) { - } + attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; - // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. - modifiedAttribute = null; - var array = attribute.array; + } - if ( attributeName === 'JOINTS_0' && - ! ( array instanceof Uint16Array ) && - ! ( array instanceof Uint8Array ) ) { + // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + modifiedAttribute = null; + const array = attribute.array; - console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); - modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); + if ( attributeName === 'JOINTS_0' && + ! ( array instanceof Uint16Array ) && + ! ( array instanceof Uint8Array ) ) { - } + console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); + modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); - var accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); + } - if ( accessor !== null ) { + const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); - attributes[ attributeName ] = accessor; - cache.attributes.set( this.getUID( attribute ), accessor ); + if ( accessor !== null ) { - } + attributes[ attributeName ] = accessor; + cache.attributes.set( this.getUID( attribute ), accessor ); } - if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); + } - // Skip if no exportable attributes found - if ( Object.keys( attributes ).length === 0 ) return null; + if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); - // Morph targets - if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { + // Skip if no exportable attributes found + if ( Object.keys( attributes ).length === 0 ) return null; - var weights = []; - var targetNames = []; - var reverseDictionary = {}; + // Morph targets + if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { - if ( mesh.morphTargetDictionary !== undefined ) { + const weights = []; + const targetNames = []; + const reverseDictionary = {}; - for ( var key in mesh.morphTargetDictionary ) { + if ( mesh.morphTargetDictionary !== undefined ) { - reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; + for ( const key in mesh.morphTargetDictionary ) { - } + reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; } - for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - - var target = {}; - var warned = false; + } - for ( var attributeName in geometry.morphAttributes ) { + for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. + const target = {}; + let warned = false; - if ( attributeName !== 'position' && attributeName !== 'normal' ) { + for ( const attributeName in geometry.morphAttributes ) { - if ( ! warned ) { + // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. + // Three.js doesn't support TANGENT yet. - console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); - warned = true; + if ( attributeName !== 'position' && attributeName !== 'normal' ) { - } + if ( ! warned ) { - continue; + console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); + warned = true; } - var attribute = geometry.morphAttributes[ attributeName ][ i ]; - var gltfAttributeName = attributeName.toUpperCase(); + continue; - // Three.js morph attribute has absolute values while the one of glTF has relative values. - // - // glTF 2.0 Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets + } - var baseAttribute = geometry.attributes[ attributeName ]; + const attribute = geometry.morphAttributes[ attributeName ][ i ]; + const gltfAttributeName = attributeName.toUpperCase(); - if ( cache.attributes.has( this.getUID( attribute ) ) ) { + // Three.js morph attribute has absolute values while the one of glTF has relative values. + // + // glTF 2.0 Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets - target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; + const baseAttribute = geometry.attributes[ attributeName ]; - } + if ( cache.attributes.has( this.getUID( attribute ) ) ) { - // Clones attribute not to override - var relativeAttribute = attribute.clone(); + target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute ) ); + continue; - if ( ! geometry.morphTargetsRelative ) { + } - for ( var j = 0, jl = attribute.count; j < jl; j ++ ) { + // Clones attribute not to override + const relativeAttribute = attribute.clone(); - relativeAttribute.setXYZ( - j, - attribute.getX( j ) - baseAttribute.getX( j ), - attribute.getY( j ) - baseAttribute.getY( j ), - attribute.getZ( j ) - baseAttribute.getZ( j ) - ); + if ( ! geometry.morphTargetsRelative ) { - } + for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { - } + relativeAttribute.setXYZ( + j, + attribute.getX( j ) - baseAttribute.getX( j ), + attribute.getY( j ) - baseAttribute.getY( j ), + attribute.getZ( j ) - baseAttribute.getZ( j ) + ); - target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); - cache.attributes.set( this.getUID( baseAttribute ), target[ gltfAttributeName ] ); + } } - targets.push( target ); - - weights.push( mesh.morphTargetInfluences[ i ] ); - - if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); + target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); + cache.attributes.set( this.getUID( baseAttribute ), target[ gltfAttributeName ] ); } - meshDef.weights = weights; - - if ( targetNames.length > 0 ) { + targets.push( target ); - meshDef.extras = {}; - meshDef.extras.targetNames = targetNames; + weights.push( mesh.morphTargetInfluences[ i ] ); - } + if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); } - var isMultiMaterial = Array.isArray( mesh.material ); + meshDef.weights = weights; - if ( isMultiMaterial && geometry.groups.length === 0 ) return null; + if ( targetNames.length > 0 ) { - var materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - var groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; + meshDef.extras = {}; + meshDef.extras.targetNames = targetNames; - for ( var i = 0, il = groups.length; i < il; i ++ ) { + } - var primitive = { - mode: mode, - attributes: attributes, - }; + } + + const isMultiMaterial = Array.isArray( mesh.material ); - this.serializeUserData( geometry, primitive ); + if ( isMultiMaterial && geometry.groups.length === 0 ) return null; - if ( targets.length > 0 ) primitive.targets = targets; + const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; + const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; - if ( geometry.index !== null ) { + for ( let i = 0, il = groups.length; i < il; i ++ ) { - var cacheKey = this.getUID( geometry.index ); + const primitive = { + mode: mode, + attributes: attributes, + }; - if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { + this.serializeUserData( geometry, primitive ); - cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; + if ( targets.length > 0 ) primitive.targets = targets; - } + if ( geometry.index !== null ) { - if ( cache.attributes.has( cacheKey ) ) { + let cacheKey = this.getUID( geometry.index ); - primitive.indices = cache.attributes.get( cacheKey ); + if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { - } else { + cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; - primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); - cache.attributes.set( cacheKey, primitive.indices ); + } - } + if ( cache.attributes.has( cacheKey ) ) { - if ( primitive.indices === null ) delete primitive.indices; + primitive.indices = cache.attributes.get( cacheKey ); - } + } else { - var material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); + primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); + cache.attributes.set( cacheKey, primitive.indices ); - if ( material !== null ) primitive.material = material; + } - primitives.push( primitive ); + if ( primitive.indices === null ) delete primitive.indices; } - meshDef.primitives = primitives; + const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); - if ( ! json.meshes ) json.meshes = []; + if ( material !== null ) primitive.material = material; - this._invokeAll( function ( ext ) { + primitives.push( primitive ); - ext.writeMesh && ext.writeMesh( mesh, meshDef ); + } - } ); + meshDef.primitives = primitives; - var index = json.meshes.push( meshDef ) - 1; - cache.meshes.set( meshCacheKey, index ); - return index; + if ( ! json.meshes ) json.meshes = []; - }, + this._invokeAll( function ( ext ) { - /** - * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {Integer} Index of the processed mesh in the "camera" array - */ - processCamera: function ( camera ) { + ext.writeMesh && ext.writeMesh( mesh, meshDef ); - var json = this.json; + } ); - if ( ! json.cameras ) json.cameras = []; + const index = json.meshes.push( meshDef ) - 1; + cache.meshes.set( meshCacheKey, index ); + return index; - var isOrtho = camera.isOrthographicCamera; + } - var cameraDef = { - type: isOrtho ? 'orthographic' : 'perspective' - }; + /** + * Process camera + * @param {THREE.Camera} camera Camera to process + * @return {Integer} Index of the processed mesh in the "camera" array + */ + processCamera( camera ) { - if ( isOrtho ) { + const json = this.json; - cameraDef.orthographic = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + if ( ! json.cameras ) json.cameras = []; - } else { + const isOrtho = camera.isOrthographicCamera; - cameraDef.perspective = { - aspectRatio: camera.aspect, - yfov: MathUtils.degToRad( camera.fov ), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; + const cameraDef = { + type: isOrtho ? 'orthographic' : 'perspective' + }; - } + if ( isOrtho ) { - // Question: Is saving "type" as name intentional? - if ( camera.name !== '' ) cameraDef.name = camera.type; + cameraDef.orthographic = { + xmag: camera.right * 2, + ymag: camera.top * 2, + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - return json.cameras.push( cameraDef ) - 1; + } else { - }, + cameraDef.perspective = { + aspectRatio: camera.aspect, + yfov: MathUtils.degToRad( camera.fov ), + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near + }; - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {THREE.AnimationClip} clip - * @param {THREE.Object3D} root - * @return {number|null} - */ - processAnimation: function ( clip, root ) { + } - var json = this.json; - var nodeMap = this.nodeMap; + // Question: Is saving "type" as name intentional? + if ( camera.name !== '' ) cameraDef.name = camera.type; - if ( ! json.animations ) json.animations = []; + return json.cameras.push( cameraDef ) - 1; - clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); + } - var tracks = clip.tracks; - var channels = []; - var samplers = []; + /** + * Creates glTF animation entry from AnimationClip object. + * + * Status: + * - Only properties listed in PATH_PROPERTIES may be animated. + * + * @param {THREE.AnimationClip} clip + * @param {THREE.Object3D} root + * @return {number|null} + */ + processAnimation( clip, root ) { - for ( var i = 0; i < tracks.length; ++ i ) { + const json = this.json; + const nodeMap = this.nodeMap; - var track = tracks[ i ]; - var trackBinding = PropertyBinding.parseTrackName( track.name ); - var trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); - var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; + if ( ! json.animations ) json.animations = []; - if ( trackBinding.objectName === 'bones' ) { + clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); - if ( trackNode.isSkinnedMesh === true ) { + const tracks = clip.tracks; + const channels = []; + const samplers = []; - trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); + for ( let i = 0; i < tracks.length; ++ i ) { - } else { + const track = tracks[ i ]; + const trackBinding = PropertyBinding.parseTrackName( track.name ); + let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); + const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - trackNode = undefined; + if ( trackBinding.objectName === 'bones' ) { - } + if ( trackNode.isSkinnedMesh === true ) { - } + trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); - if ( ! trackNode || ! trackProperty ) { + } else { - console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); - return null; + trackNode = undefined; } - var inputItemSize = 1; - var outputItemSize = track.values.length / track.times.length; + } - if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { + if ( ! trackNode || ! trackProperty ) { - outputItemSize /= trackNode.morphTargetInfluences.length; + console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); + return null; - } + } - var interpolation; + const inputItemSize = 1; + let outputItemSize = track.values.length / track.times.length; - // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE + if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { - // Detecting glTF cubic spline interpolant by checking factory method's special property - // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return - // valid value from .getInterpolation(). - if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { + outputItemSize /= trackNode.morphTargetInfluences.length; - interpolation = 'CUBICSPLINE'; + } - // itemSize of CUBICSPLINE keyframe is 9 - // (VEC3 * 3: inTangent, splineVertex, and outTangent) - // but needs to be stored as VEC3 so dividing by 3 here. - outputItemSize /= 3; + let interpolation; - } else if ( track.getInterpolation() === InterpolateDiscrete ) { + // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - interpolation = 'STEP'; + // Detecting glTF cubic spline interpolant by checking factory method's special property + // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return + // valid value from .getInterpolation(). + if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { - } else { + interpolation = 'CUBICSPLINE'; - interpolation = 'LINEAR'; + // itemSize of CUBICSPLINE keyframe is 9 + // (VEC3 * 3: inTangent, splineVertex, and outTangent) + // but needs to be stored as VEC3 so dividing by 3 here. + outputItemSize /= 3; - } + } else if ( track.getInterpolation() === InterpolateDiscrete ) { - samplers.push( { - input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), - output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), - interpolation: interpolation - } ); - - channels.push( { - sampler: samplers.length - 1, - target: { - node: nodeMap.get( trackNode ), - path: trackProperty - } - } ); + interpolation = 'STEP'; + + } else { + + interpolation = 'LINEAR'; } - json.animations.push( { - name: clip.name || 'clip_' + json.animations.length, - samplers: samplers, - channels: channels + samplers.push( { + input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), + output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), + interpolation: interpolation } ); - return json.animations.length - 1; + channels.push( { + sampler: samplers.length - 1, + target: { + node: nodeMap.get( trackNode ), + path: trackProperty + } + } ); - }, + } - /** - * @param {THREE.Object3D} object - * @return {number|null} - */ - processSkin: function ( object ) { + json.animations.push( { + name: clip.name || 'clip_' + json.animations.length, + samplers: samplers, + channels: channels + } ); - var json = this.json; - var nodeMap = this.nodeMap; + return json.animations.length - 1; - var node = json.nodes[ nodeMap.get( object ) ]; + } - var skeleton = object.skeleton; + /** + * @param {THREE.Object3D} object + * @return {number|null} + */ + processSkin( object ) { - if ( skeleton === undefined ) return null; + const json = this.json; + const nodeMap = this.nodeMap; - var rootJoint = object.skeleton.bones[ 0 ]; + const node = json.nodes[ nodeMap.get( object ) ]; - if ( rootJoint === undefined ) return null; + const skeleton = object.skeleton; - var joints = []; - var inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); - var temporaryBoneInverse = new Matrix4(); + if ( skeleton === undefined ) return null; - for ( var i = 0; i < skeleton.bones.length; ++ i ) { + const rootJoint = object.skeleton.bones[ 0 ]; - joints.push( nodeMap.get( skeleton.bones[ i ] ) ); - temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); - temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); + if ( rootJoint === undefined ) return null; - } + const joints = []; + const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); + const temporaryBoneInverse = new Matrix4(); - if ( json.skins === undefined ) json.skins = []; + for ( let i = 0; i < skeleton.bones.length; ++ i ) { - json.skins.push( { - inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), - joints: joints, - skeleton: nodeMap.get( rootJoint ) - } ); + joints.push( nodeMap.get( skeleton.bones[ i ] ) ); + temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); + temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); - var skinIndex = node.skin = json.skins.length - 1; + } - return skinIndex; + if ( json.skins === undefined ) json.skins = []; - }, + json.skins.push( { + inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), + joints: joints, + skeleton: nodeMap.get( rootJoint ) + } ); - /** - * Process Object3D node - * @param {THREE.Object3D} node Object3D to processNode - * @return {Integer} Index of the node in the nodes list - */ - processNode: function ( object ) { + const skinIndex = node.skin = json.skins.length - 1; - var json = this.json; - var options = this.options; - var nodeMap = this.nodeMap; + return skinIndex; - if ( ! json.nodes ) json.nodes = []; + } - var nodeDef = {}; + /** + * Process Object3D node + * @param {THREE.Object3D} node Object3D to processNode + * @return {Integer} Index of the node in the nodes list + */ + processNode( object ) { - if ( options.trs ) { + const json = this.json; + const options = this.options; + const nodeMap = this.nodeMap; - var rotation = object.quaternion.toArray(); - var position = object.position.toArray(); - var scale = object.scale.toArray(); + if ( ! json.nodes ) json.nodes = []; - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { + const nodeDef = {}; - nodeDef.rotation = rotation; + if ( options.trs ) { - } + const rotation = object.quaternion.toArray(); + const position = object.position.toArray(); + const scale = object.scale.toArray(); - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { + if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - nodeDef.translation = position; + nodeDef.rotation = rotation; - } + } - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { + if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - nodeDef.scale = scale; + nodeDef.translation = position; - } + } - } else { + if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - if ( object.matrixAutoUpdate ) { + nodeDef.scale = scale; - object.updateMatrix(); + } - } + } else { - if ( isIdentityMatrix( object.matrix ) === false ) { + if ( object.matrixAutoUpdate ) { - nodeDef.matrix = object.matrix.elements; + object.updateMatrix(); - } + } + + if ( isIdentityMatrix( object.matrix ) === false ) { + + nodeDef.matrix = object.matrix.elements; } - // We don't export empty strings name because it represents no-name in Three.js. - if ( object.name !== '' ) nodeDef.name = String( object.name ); + } - this.serializeUserData( object, nodeDef ); + // We don't export empty strings name because it represents no-name in Three.js. + if ( object.name !== '' ) nodeDef.name = String( object.name ); - if ( object.isMesh || object.isLine || object.isPoints ) { + this.serializeUserData( object, nodeDef ); - var meshIndex = this.processMesh( object ); + if ( object.isMesh || object.isLine || object.isPoints ) { - if ( meshIndex !== null ) nodeDef.mesh = meshIndex; + const meshIndex = this.processMesh( object ); - } else if ( object.isCamera ) { + if ( meshIndex !== null ) nodeDef.mesh = meshIndex; - nodeDef.camera = this.processCamera( object ); + } else if ( object.isCamera ) { - } + nodeDef.camera = this.processCamera( object ); - if ( object.isSkinnedMesh ) this.skins.push( object ); + } - if ( object.children.length > 0 ) { + if ( object.isSkinnedMesh ) this.skins.push( object ); - var children = []; + if ( object.children.length > 0 ) { - for ( var i = 0, l = object.children.length; i < l; i ++ ) { + const children = []; - var child = object.children[ i ]; + for ( let i = 0, l = object.children.length; i < l; i ++ ) { - if ( child.visible || options.onlyVisible === false ) { + const child = object.children[ i ]; - var nodeIndex = this.processNode( child ); + if ( child.visible || options.onlyVisible === false ) { - if ( nodeIndex !== null ) children.push( nodeIndex ); + const nodeIndex = this.processNode( child ); - } + if ( nodeIndex !== null ) children.push( nodeIndex ); } - if ( children.length > 0 ) nodeDef.children = children; - } - this._invokeAll( function ( ext ) { + if ( children.length > 0 ) nodeDef.children = children; - ext.writeNode && ext.writeNode( object, nodeDef ); + } - } ); + this._invokeAll( function ( ext ) { - var nodeIndex = json.nodes.push( nodeDef ) - 1; - nodeMap.set( object, nodeIndex ); - return nodeIndex; + ext.writeNode && ext.writeNode( object, nodeDef ); - }, + } ); - /** - * Process Scene - * @param {Scene} node Scene to process - */ - processScene: function ( scene ) { + const nodeIndex = json.nodes.push( nodeDef ) - 1; + nodeMap.set( object, nodeIndex ); + return nodeIndex; - var json = this.json; - var options = this.options; + } - if ( ! json.scenes ) { + /** + * Process Scene + * @param {Scene} node Scene to process + */ + processScene( scene ) { - json.scenes = []; - json.scene = 0; + const json = this.json; + const options = this.options; - } + if ( ! json.scenes ) { - var sceneDef = {}; + json.scenes = []; + json.scene = 0; - if ( scene.name !== '' ) sceneDef.name = scene.name; + } - json.scenes.push( sceneDef ); + const sceneDef = {}; - var nodes = []; + if ( scene.name !== '' ) sceneDef.name = scene.name; - for ( var i = 0, l = scene.children.length; i < l; i ++ ) { + json.scenes.push( sceneDef ); - var child = scene.children[ i ]; + const nodes = []; - if ( child.visible || options.onlyVisible === false ) { + for ( let i = 0, l = scene.children.length; i < l; i ++ ) { - var nodeIndex = this.processNode( child ); + const child = scene.children[ i ]; - if ( nodeIndex !== null ) nodes.push( nodeIndex ); + if ( child.visible || options.onlyVisible === false ) { - } + const nodeIndex = this.processNode( child ); + + if ( nodeIndex !== null ) nodes.push( nodeIndex ); } - if ( nodes.length > 0 ) sceneDef.nodes = nodes; + } - this.serializeUserData( scene, sceneDef ); + if ( nodes.length > 0 ) sceneDef.nodes = nodes; - }, + this.serializeUserData( scene, sceneDef ); - /** - * Creates a Scene to hold a list of objects and parse it - * @param {Array} objects List of objects to process - */ - processObjects: function ( objects ) { + } - var scene = new Scene(); - scene.name = 'AuxScene'; + /** + * Creates a Scene to hold a list of objects and parse it + * @param {Array} objects List of objects to process + */ + processObjects( objects ) { - for ( var i = 0; i < objects.length; i ++ ) { + const scene = new Scene(); + scene.name = 'AuxScene'; - // We push directly to children instead of calling `add` to prevent - // modify the .parent and break its original scene and hierarchy - scene.children.push( objects[ i ] ); + for ( let i = 0; i < objects.length; i ++ ) { - } + // We push directly to children instead of calling `add` to prevent + // modify the .parent and break its original scene and hierarchy + scene.children.push( objects[ i ] ); - this.processScene( scene ); + } - }, + this.processScene( scene ); - /** - * @param {THREE.Object3D|Array} input - */ - processInput: function ( input ) { + } - var options = this.options; + /** + * @param {THREE.Object3D|Array} input + */ + processInput( input ) { - input = input instanceof Array ? input : [ input ]; + const options = this.options; - this._invokeAll( function ( ext ) { + input = input instanceof Array ? input : [ input ]; - ext.beforeParse && ext.beforeParse( input ); + this._invokeAll( function ( ext ) { - } ); + ext.beforeParse && ext.beforeParse( input ); - var objectsWithoutScene = []; + } ); - for ( var i = 0; i < input.length; i ++ ) { + const objectsWithoutScene = []; - if ( input[ i ] instanceof Scene ) { + for ( let i = 0; i < input.length; i ++ ) { - this.processScene( input[ i ] ); + if ( input[ i ] instanceof Scene ) { - } else { + this.processScene( input[ i ] ); - objectsWithoutScene.push( input[ i ] ); + } else { - } + objectsWithoutScene.push( input[ i ] ); } - if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); + } - for ( var i = 0; i < this.skins.length; ++ i ) { + if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); - this.processSkin( this.skins[ i ] ); + for ( let i = 0; i < this.skins.length; ++ i ) { - } + this.processSkin( this.skins[ i ] ); - for ( var i = 0; i < options.animations.length; ++ i ) { + } - this.processAnimation( options.animations[ i ], input[ 0 ] ); + for ( let i = 0; i < options.animations.length; ++ i ) { - } + this.processAnimation( options.animations[ i ], input[ 0 ] ); - this._invokeAll( function ( ext ) { + } - ext.afterParse && ext.afterParse( input ); + this._invokeAll( function ( ext ) { - } ); + ext.afterParse && ext.afterParse( input ); - }, + } ); - _invokeAll: function ( func ) { + } - for ( var i = 0, il = this.plugins.length; i < il; i ++ ) { + _invokeAll( func ) { - func( this.plugins[ i ] ); + for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { - } + func( this.plugins[ i ] ); } - }; + } - /** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ - function GLTFLightExtension( writer ) { +} + +/** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ +class GLTFLightExtension { + + constructor( writer ) { this.writer = writer; this.name = 'KHR_lights_punctual'; } - GLTFLightExtension.prototype = { - - constructor: GLTFLightExtension, + writeNode( light, nodeDef ) { - writeNode: function ( light, nodeDef ) { + if ( ! light.isLight ) return; - if ( ! light.isLight ) return; + if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { - if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { + console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); + return; - console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); - return; + } - } + const writer = this.writer; + const json = writer.json; + const extensionsUsed = writer.extensionsUsed; - var writer = this.writer; - var json = writer.json; - var extensionsUsed = writer.extensionsUsed; + const lightDef = {}; - var lightDef = {}; + if ( light.name ) lightDef.name = light.name; - if ( light.name ) lightDef.name = light.name; + lightDef.color = light.color.toArray(); - lightDef.color = light.color.toArray(); + lightDef.intensity = light.intensity; - lightDef.intensity = light.intensity; + if ( light.isDirectionalLight ) { - if ( light.isDirectionalLight ) { + lightDef.type = 'directional'; - lightDef.type = 'directional'; + } else if ( light.isPointLight ) { - } else if ( light.isPointLight ) { + lightDef.type = 'point'; - lightDef.type = 'point'; + if ( light.distance > 0 ) lightDef.range = light.distance; - if ( light.distance > 0 ) lightDef.range = light.distance; + } else if ( light.isSpotLight ) { - } else if ( light.isSpotLight ) { + lightDef.type = 'spot'; - lightDef.type = 'spot'; + if ( light.distance > 0 ) lightDef.range = light.distance; - if ( light.distance > 0 ) lightDef.range = light.distance; + lightDef.spot = {}; + lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; + lightDef.spot.outerConeAngle = light.angle; - lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; - lightDef.spot.outerConeAngle = light.angle; + } - } + if ( light.decay !== undefined && light.decay !== 2 ) { - if ( light.decay !== undefined && light.decay !== 2 ) { + console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + + 'and expects light.decay=2.' ); - console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' - + 'and expects light.decay=2.' ); + } - } + if ( light.target + && ( light.target.parent !== light + || light.target.position.x !== 0 + || light.target.position.y !== 0 + || light.target.position.z !== - 1 ) ) { - if ( light.target - && ( light.target.parent !== light - || light.target.position.x !== 0 - || light.target.position.y !== 0 - || light.target.position.z !== - 1 ) ) { + console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + + 'make light.target a child of the light with position 0,0,-1.' ); - console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' - + 'make light.target a child of the light with position 0,0,-1.' ); + } - } + if ( ! extensionsUsed[ this.name ] ) { - if ( ! extensionsUsed[ this.name ] ) { + json.extensions = json.extensions || {}; + json.extensions[ this.name ] = { lights: [] }; + extensionsUsed[ this.name ] = true; - json.extensions = json.extensions || {}; - json.extensions[ this.name ] = { lights: [] }; - extensionsUsed[ this.name ] = true; + } - } + const lights = json.extensions[ this.name ].lights; + lights.push( lightDef ); - var lights = json.extensions[ this.name ].lights; - lights.push( lightDef ); + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; + } - } +} - }; +/** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ +class GLTFMaterialsUnlitExtension { - /** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ - function GLTFMaterialsUnlitExtension( writer ) { + constructor( writer ) { this.writer = writer; this.name = 'KHR_materials_unlit'; } - GLTFMaterialsUnlitExtension.prototype = { + writeMaterial( material, materialDef ) { - constructor: GLTFMaterialsUnlitExtension, + if ( ! material.isMeshBasicMaterial ) return; - writeMaterial: function ( material, materialDef ) { + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - if ( ! material.isMeshBasicMaterial ) return; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = {}; - var writer = this.writer; - var extensionsUsed = writer.extensionsUsed; + extensionsUsed[ this.name ] = true; - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = {}; + materialDef.pbrMetallicRoughness.metallicFactor = 0.0; + materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; - extensionsUsed[ this.name ] = true; - - materialDef.pbrMetallicRoughness.metallicFactor = 0.0; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; + } - } +} - }; +/** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness + */ +class GLTFMaterialsPBRSpecularGlossiness { - /** - * Specular-Glossiness Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness - */ - function GLTFMaterialsPBRSpecularGlossiness( writer ) { + constructor( writer ) { this.writer = writer; this.name = 'KHR_materials_pbrSpecularGlossiness'; } - GLTFMaterialsPBRSpecularGlossiness.prototype = { - - constructor: GLTFMaterialsPBRSpecularGlossiness, + writeMaterial( material, materialDef ) { - writeMaterial: function ( material, materialDef ) { + if ( ! material.isGLTFSpecularGlossinessMaterial ) return; - if ( ! material.isGLTFSpecularGlossinessMaterial ) return; + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; - var writer = this.writer; - var extensionsUsed = writer.extensionsUsed; + const extensionDef = {}; - var extensionDef = {}; + if ( materialDef.pbrMetallicRoughness.baseColorFactor ) { - if ( materialDef.pbrMetallicRoughness.baseColorFactor ) { + extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor; - extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor; - - } - - var specularFactor = [ 1, 1, 1 ]; - material.specular.toArray( specularFactor, 0 ); - extensionDef.specularFactor = specularFactor; - extensionDef.glossinessFactor = material.glossiness; - - if ( materialDef.pbrMetallicRoughness.baseColorTexture ) { + } - extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture; + const specularFactor = [ 1, 1, 1 ]; + material.specular.toArray( specularFactor, 0 ); + extensionDef.specularFactor = specularFactor; + extensionDef.glossinessFactor = material.glossiness; - } + if ( materialDef.pbrMetallicRoughness.baseColorTexture ) { - if ( material.specularMap ) { + extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture; - var specularMapDef = { index: writer.processTexture( material.specularMap ) }; - writer.applyTextureTransform( specularMapDef, material.specularMap ); - extensionDef.specularGlossinessTexture = specularMapDef; + } - } + if ( material.specularMap ) { - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - extensionsUsed[ this.name ] = true; + const specularMapDef = { index: writer.processTexture( material.specularMap ) }; + writer.applyTextureTransform( specularMapDef, material.specularMap ); + extensionDef.specularGlossinessTexture = specularMapDef; } - }; + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + extensionsUsed[ this.name ] = true; - /** - * Static utility functions - */ - GLTFExporter.Utils = { + } - insertKeyframe: function ( track, time ) { +} - var tolerance = 0.001; // 1ms - var valueSize = track.getValueSize(); +/** + * Static utility functions + */ +GLTFExporter.Utils = { - var times = new track.TimeBufferType( track.times.length + 1 ); - var values = new track.ValueBufferType( track.values.length + valueSize ); - var interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + insertKeyframe: function ( track, time ) { - var index; + const tolerance = 0.001; // 1ms + const valueSize = track.getValueSize(); - if ( track.times.length === 0 ) { + const times = new track.TimeBufferType( track.times.length + 1 ); + const values = new track.ValueBufferType( track.values.length + valueSize ); + const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - times[ 0 ] = time; + let index; - for ( var i = 0; i < valueSize; i ++ ) { + if ( track.times.length === 0 ) { - values[ i ] = 0; + times[ 0 ] = time; - } + for ( let i = 0; i < valueSize; i ++ ) { - index = 0; + values[ i ] = 0; - } else if ( time < track.times[ 0 ] ) { + } - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + index = 0; - times[ 0 ] = time; - times.set( track.times, 1 ); + } else if ( time < track.times[ 0 ] ) { - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - index = 0; + times[ 0 ] = time; + times.set( track.times, 1 ); - } else if ( time > track.times[ track.times.length - 1 ] ) { + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + index = 0; - return track.times.length - 1; + } else if ( time > track.times[ track.times.length - 1 ] ) { - } + if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); + return track.times.length - 1; - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); + } - index = times.length - 1; + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); - } else { + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); - for ( var i = 0; i < track.times.length; i ++ ) { + index = times.length - 1; - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + } else { - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + for ( let i = 0; i < track.times.length; i ++ ) { - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - index = i + 1; + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); - break; + values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); + values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); + values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - } + index = i + 1; + + break; } } - track.times = times; - track.values = values; - - return index; - - }, + } - mergeMorphTargetTracks: function ( clip, root ) { + track.times = times; + track.values = values; - var tracks = []; - var mergedTracks = {}; - var sourceTracks = clip.tracks; + return index; - for ( var i = 0; i < sourceTracks.length; ++ i ) { + }, - var sourceTrack = sourceTracks[ i ]; - var sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - var sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + mergeMorphTargetTracks: function ( clip, root ) { - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + const tracks = []; + const mergedTracks = {}; + const sourceTracks = clip.tracks; - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; + for ( let i = 0; i < sourceTracks.length; ++ i ) { - } + let sourceTrack = sourceTracks[ i ]; + const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { + if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push( sourceTrack ); + continue; - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + } - } + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); } - var targetCount = sourceTrackNode.morphTargetInfluences.length; - var targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - if ( targetIndex === undefined ) { + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - - } + } - var mergedTrack; + const targetCount = sourceTrackNode.morphTargetInfluences.length; + const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { + if ( targetIndex === undefined ) { - mergedTrack = sourceTrack.clone(); + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - var values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + } - for ( var j = 0; j < mergedTrack.times.length; j ++ ) { + let mergedTrack; - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - } + mergedTrack = sourceTrack.clone(); - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; + const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - continue; + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; } - var sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; + mergedTrack.values = values; - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( var j = 0; j < mergedTrack.times.length; j ++ ) { + continue; - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + } - } + const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( var j = 0; j < sourceTrack.times.length; j ++ ) { + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - var keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - } + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); } - clip.tracks = tracks; + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - return clip; + const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + + } } - }; + clip.tracks = tracks; + + return clip; - return GLTFExporter; + } -} )(); +}; export { GLTFExporter }; diff --git a/examples/jsm/exporters/MMDExporter.js b/examples/jsm/exporters/MMDExporter.js index ba68d16cc45390..15b24e357d5261 100644 --- a/examples/jsm/exporters/MMDExporter.js +++ b/examples/jsm/exporters/MMDExporter.js @@ -10,71 +10,7 @@ import { MMDParser } from '../libs/mmdparser.module.js'; * - mmd-parser https://github.com/takahirox/mmd-parser */ -var MMDExporter = function () { - - // Unicode to Shift_JIS table - var u2sTable; - - function unicodeToShiftjis( str ) { - - if ( u2sTable === undefined ) { - - var encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef - var table = encoder.s2uTable; - u2sTable = {}; - - var keys = Object.keys( table ); - - for ( var i = 0, il = keys.length; i < il; i ++ ) { - - var key = keys[ i ]; - - var value = table[ key ]; - key = parseInt( key ); - - u2sTable[ value ] = key; - - } - - } - - var array = []; - - for ( var i = 0, il = str.length; i < il; i ++ ) { - - var code = str.charCodeAt( i ); - - var value = u2sTable[ code ]; - - if ( value === undefined ) { - - throw 'cannot convert charcode 0x' + code.toString( 16 ); - - } else if ( value > 0xff ) { - - array.push( ( value >> 8 ) & 0xff ); - array.push( value & 0xff ); - - } else { - - array.push( value & 0xff ); - - } - - } - - return new Uint8Array( array ); - - } - - function getBindBones( skin ) { - - // any more efficient ways? - var poseSkin = skin.clone(); - poseSkin.pose(); - return poseSkin.skeleton.bones; - - } +class MMDExporter { /* TODO: implement // mesh -> pmd @@ -90,11 +26,18 @@ var MMDExporter = function () { }; */ + /* TODO: implement + // animation + skeleton -> vmd + this.parseVmd = function ( object ) { + + }; + */ + /* * skeleton -> vpd * Returns Shift_JIS encoded Uint8Array. Otherwise return strings. */ - this.parseVpd = function ( skin, outputShiftJis, useOriginalBones ) { + parseVpd( skin, outputShiftJis, useOriginalBones ) { if ( skin.isSkinnedMesh !== true ) { @@ -107,7 +50,7 @@ var MMDExporter = function () { if ( Math.abs( num ) < 1e-6 ) num = 0; - var a = num.toString(); + let a = num.toString(); if ( a.indexOf( '.' ) === - 1 ) { @@ -117,10 +60,10 @@ var MMDExporter = function () { a += '000000'; - var index = a.indexOf( '.' ); + const index = a.indexOf( '.' ); - var d = a.slice( 0, index ); - var p = a.slice( index + 1, index + 7 ); + const d = a.slice( 0, index ); + const p = a.slice( index + 1, index + 7 ); return d + '.' + p; @@ -128,9 +71,9 @@ var MMDExporter = function () { function toStringsFromArray( array ) { - var a = []; + const a = []; - for ( var i = 0, il = array.length; i < il; i ++ ) { + for ( let i = 0, il = array.length; i < il; i ++ ) { a.push( toStringsFromNumber( array[ i ] ) ); @@ -142,25 +85,25 @@ var MMDExporter = function () { skin.updateMatrixWorld( true ); - var bones = skin.skeleton.bones; - var bones2 = getBindBones( skin ); + const bones = skin.skeleton.bones; + const bones2 = getBindBones( skin ); - var position = new Vector3(); - var quaternion = new Quaternion(); - var quaternion2 = new Quaternion(); - var matrix = new Matrix4(); + const position = new Vector3(); + const quaternion = new Quaternion(); + const quaternion2 = new Quaternion(); + const matrix = new Matrix4(); - var array = []; + const array = []; array.push( 'Vocaloid Pose Data file' ); array.push( '' ); array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' ); array.push( bones.length + ';' ); array.push( '' ); - for ( var i = 0, il = bones.length; i < il; i ++ ) { + for ( let i = 0, il = bones.length; i < il; i ++ ) { - var bone = bones[ i ]; - var bone2 = bones2[ i ]; + const bone = bones[ i ]; + const bone2 = bones2[ i ]; /* * use the bone matrix saved before solving IK. @@ -181,8 +124,8 @@ var MMDExporter = function () { position.setFromMatrixPosition( matrix ); quaternion.setFromRotationMatrix( matrix ); - var pArray = position.sub( bone2.position ).toArray(); - var qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray(); + const pArray = position.sub( bone2.position ).toArray(); + const qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray(); // right to left pArray[ 2 ] = - pArray[ 2 ]; @@ -199,19 +142,76 @@ var MMDExporter = function () { array.push( '' ); - var lines = array.join( '\n' ); + const lines = array.join( '\n' ); return ( outputShiftJis === true ) ? unicodeToShiftjis( lines ) : lines; - }; + } - /* TODO: implement - // animation + skeleton -> vmd - this.parseVmd = function ( object ) { +} - }; - */ +// Unicode to Shift_JIS table +let u2sTable; + +function unicodeToShiftjis( str ) { + + if ( u2sTable === undefined ) { + + const encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef + const table = encoder.s2uTable; + u2sTable = {}; + + const keys = Object.keys( table ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + let key = keys[ i ]; + + const value = table[ key ]; + key = parseInt( key ); + + u2sTable[ value ] = key; + + } + + } + + const array = []; + + for ( let i = 0, il = str.length; i < il; i ++ ) { + + const code = str.charCodeAt( i ); + + const value = u2sTable[ code ]; + + if ( value === undefined ) { + + throw 'cannot convert charcode 0x' + code.toString( 16 ); + + } else if ( value > 0xff ) { + + array.push( ( value >> 8 ) & 0xff ); + array.push( value & 0xff ); + + } else { + + array.push( value & 0xff ); + + } + + } + + return new Uint8Array( array ); + +} + +function getBindBones( skin ) { + + // any more efficient ways? + const poseSkin = skin.clone(); + poseSkin.pose(); + return poseSkin.skeleton.bones; -}; +} export { MMDExporter }; diff --git a/examples/jsm/exporters/OBJExporter.js b/examples/jsm/exporters/OBJExporter.js index 81ebecb4b523dc..65fac646565d8b 100644 --- a/examples/jsm/exporters/OBJExporter.js +++ b/examples/jsm/exporters/OBJExporter.js @@ -5,36 +5,32 @@ import { Vector3 } from '../../../build/three.module.js'; -var OBJExporter = function () {}; +class OBJExporter { -OBJExporter.prototype = { + parse( object ) { - constructor: OBJExporter, + let output = ''; - parse: function ( object ) { + let indexVertex = 0; + let indexVertexUvs = 0; + let indexNormals = 0; - var output = ''; + const vertex = new Vector3(); + const color = new Color(); + const normal = new Vector3(); + const uv = new Vector2(); - var indexVertex = 0; - var indexVertexUvs = 0; - var indexNormals = 0; + const face = []; - var vertex = new Vector3(); - var color = new Color(); - var normal = new Vector3(); - var uv = new Vector2(); + function parseMesh( mesh ) { - var i, j, k, l, m, face = []; + let nbVertex = 0; + let nbNormals = 0; + let nbVertexUvs = 0; - var parseMesh = function ( mesh ) { + const geometry = mesh.geometry; - var nbVertex = 0; - var nbNormals = 0; - var nbVertexUvs = 0; - - var geometry = mesh.geometry; - - var normalMatrixWorld = new Matrix3(); + const normalMatrixWorld = new Matrix3(); if ( geometry.isBufferGeometry !== true ) { @@ -43,10 +39,10 @@ OBJExporter.prototype = { } // shortcuts - var vertices = geometry.getAttribute( 'position' ); - var normals = geometry.getAttribute( 'normal' ); - var uvs = geometry.getAttribute( 'uv' ); - var indices = geometry.getIndex(); + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const indices = geometry.getIndex(); // name of the mesh object output += 'o ' + mesh.name + '\n'; @@ -62,7 +58,7 @@ OBJExporter.prototype = { if ( vertices !== undefined ) { - for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { vertex.x = vertices.getX( i ); vertex.y = vertices.getY( i ); @@ -82,7 +78,7 @@ OBJExporter.prototype = { if ( uvs !== undefined ) { - for ( i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) { + for ( let i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) { uv.x = uvs.getX( i ); uv.y = uvs.getY( i ); @@ -100,7 +96,7 @@ OBJExporter.prototype = { normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); - for ( i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) { + for ( let i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) { normal.x = normals.getX( i ); normal.y = normals.getY( i ); @@ -120,11 +116,11 @@ OBJExporter.prototype = { if ( indices !== null ) { - for ( i = 0, l = indices.count; i < l; i += 3 ) { + for ( let i = 0, l = indices.count; i < l; i += 3 ) { - for ( m = 0; m < 3; m ++ ) { + for ( let m = 0; m < 3; m ++ ) { - j = indices.getX( i + m ) + 1; + const j = indices.getX( i + m ) + 1; face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' ); @@ -137,11 +133,11 @@ OBJExporter.prototype = { } else { - for ( i = 0, l = vertices.count; i < l; i += 3 ) { + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { - for ( m = 0; m < 3; m ++ ) { + for ( let m = 0; m < 3; m ++ ) { - j = i + m + 1; + const j = i + m + 1; face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' ); @@ -159,14 +155,14 @@ OBJExporter.prototype = { indexVertexUvs += nbVertexUvs; indexNormals += nbNormals; - }; + } - var parseLine = function ( line ) { + function parseLine( line ) { - var nbVertex = 0; + let nbVertex = 0; - var geometry = line.geometry; - var type = line.type; + const geometry = line.geometry; + const type = line.type; if ( geometry.isBufferGeometry !== true ) { @@ -175,14 +171,14 @@ OBJExporter.prototype = { } // shortcuts - var vertices = geometry.getAttribute( 'position' ); + const vertices = geometry.getAttribute( 'position' ); // name of the line object output += 'o ' + line.name + '\n'; if ( vertices !== undefined ) { - for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { vertex.x = vertices.getX( i ); vertex.y = vertices.getY( i ); @@ -202,7 +198,7 @@ OBJExporter.prototype = { output += 'l '; - for ( j = 1, l = vertices.count; j <= l; j ++ ) { + for ( let j = 1, l = vertices.count; j <= l; j ++ ) { output += ( indexVertex + j ) + ' '; @@ -214,7 +210,7 @@ OBJExporter.prototype = { if ( type === 'LineSegments' ) { - for ( j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) { + for ( let j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) { output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n'; @@ -225,13 +221,13 @@ OBJExporter.prototype = { // update index indexVertex += nbVertex; - }; + } - var parsePoints = function ( points ) { + function parsePoints( points ) { - var nbVertex = 0; + let nbVertex = 0; - var geometry = points.geometry; + const geometry = points.geometry; if ( geometry.isBufferGeometry !== true ) { @@ -239,14 +235,14 @@ OBJExporter.prototype = { } - var vertices = geometry.getAttribute( 'position' ); - var colors = geometry.getAttribute( 'color' ); + const vertices = geometry.getAttribute( 'position' ); + const colors = geometry.getAttribute( 'color' ); output += 'o ' + points.name + '\n'; if ( vertices !== undefined ) { - for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { vertex.fromBufferAttribute( vertices, i ); vertex.applyMatrix4( points.matrixWorld ); @@ -269,7 +265,7 @@ OBJExporter.prototype = { output += 'p '; - for ( j = 1, l = vertices.count; j <= l; j ++ ) { + for ( let j = 1, l = vertices.count; j <= l; j ++ ) { output += ( indexVertex + j ) + ' '; @@ -280,7 +276,7 @@ OBJExporter.prototype = { // update index indexVertex += nbVertex; - }; + } object.traverse( function ( child ) { @@ -308,6 +304,6 @@ OBJExporter.prototype = { } -}; +} export { OBJExporter }; diff --git a/examples/jsm/exporters/PLYExporter.js b/examples/jsm/exporters/PLYExporter.js index 63ad9a23de8404..8d7896b8971c6d 100644 --- a/examples/jsm/exporters/PLYExporter.js +++ b/examples/jsm/exporters/PLYExporter.js @@ -7,7 +7,7 @@ import { * https://github.com/gkjohnson/ply-exporter-js * * Usage: - * var exporter = new PLYExporter(); + * const exporter = new PLYExporter(); * * // second argument is a list of options * exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true }); @@ -16,13 +16,9 @@ import { * http://paulbourke.net/dataformats/ply/ */ -var PLYExporter = function () {}; +class PLYExporter { -PLYExporter.prototype = { - - constructor: PLYExporter, - - parse: function ( object, onDone, options ) { + parse( object, onDone, options ) { if ( onDone && typeof onDone === 'object' ) { @@ -39,8 +35,8 @@ PLYExporter.prototype = { if ( child.isMesh === true ) { - var mesh = child; - var geometry = mesh.geometry; + const mesh = child; + const geometry = mesh.geometry; if ( geometry.isBufferGeometry !== true ) { @@ -61,7 +57,7 @@ PLYExporter.prototype = { } // Default options - var defaultOptions = { + const defaultOptions = { binary: false, excludeAttributes: [], // normal, uv, color, index littleEndian: false @@ -69,21 +65,21 @@ PLYExporter.prototype = { options = Object.assign( defaultOptions, options ); - var excludeAttributes = options.excludeAttributes; - var includeNormals = false; - var includeColors = false; - var includeUVs = false; + const excludeAttributes = options.excludeAttributes; + let includeNormals = false; + let includeColors = false; + let includeUVs = false; // count the vertices, check which properties are used, // and cache the BufferGeometry - var vertexCount = 0; - var faceCount = 0; + let vertexCount = 0; + let faceCount = 0; object.traverse( function ( child ) { if ( child.isMesh === true ) { - var mesh = child; - var geometry = mesh.geometry; + const mesh = child; + const geometry = mesh.geometry; if ( geometry.isBufferGeometry !== true ) { @@ -91,11 +87,11 @@ PLYExporter.prototype = { } - var vertices = geometry.getAttribute( 'position' ); - var normals = geometry.getAttribute( 'normal' ); - var uvs = geometry.getAttribute( 'uv' ); - var colors = geometry.getAttribute( 'color' ); - var indices = geometry.getIndex(); + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); if ( vertices === undefined ) { @@ -116,7 +112,7 @@ PLYExporter.prototype = { } ); - var includeIndices = excludeAttributes.indexOf( 'index' ) === - 1; + const includeIndices = excludeAttributes.indexOf( 'index' ) === - 1; includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1; includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1; includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1; @@ -138,9 +134,9 @@ PLYExporter.prototype = { } - var indexByteCount = 4; + const indexByteCount = 4; - var header = + let header = 'ply\n' + `format ${ options.binary ? ( options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' ) : 'ascii' } 1.0\n` + `element vertex ${vertexCount}\n` + @@ -192,42 +188,42 @@ PLYExporter.prototype = { // Generate attribute data - var vertex = new Vector3(); - var normalMatrixWorld = new Matrix3(); - var result = null; + const vertex = new Vector3(); + const normalMatrixWorld = new Matrix3(); + let result = null; if ( options.binary === true ) { // Binary File Generation - var headerBin = new TextEncoder().encode( header ); + const headerBin = new TextEncoder().encode( header ); // 3 position values at 4 bytes // 3 normal values at 4 bytes // 3 color channels with 1 byte // 2 uv values at 4 bytes - var vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); + const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); // 1 byte shape desciptor // 3 vertex indices at ${indexByteCount} bytes - var faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0; - var output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) ); + const faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0; + const output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) ); new Uint8Array( output.buffer ).set( headerBin, 0 ); - var vOffset = headerBin.length; - var fOffset = headerBin.length + vertexListLength; - var writtenVertices = 0; + let vOffset = headerBin.length; + let fOffset = headerBin.length + vertexListLength; + let writtenVertices = 0; traverseMeshes( function ( mesh, geometry ) { - var vertices = geometry.getAttribute( 'position' ); - var normals = geometry.getAttribute( 'normal' ); - var uvs = geometry.getAttribute( 'uv' ); - var colors = geometry.getAttribute( 'color' ); - var indices = geometry.getIndex(); + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); - for ( var i = 0, l = vertices.count; i < l; i ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++ ) { vertex.x = vertices.getX( i ); vertex.y = vertices.getY( i ); @@ -341,7 +337,7 @@ PLYExporter.prototype = { if ( indices !== null ) { - for ( var i = 0, l = indices.count; i < l; i += 3 ) { + for ( let i = 0, l = indices.count; i < l; i += 3 ) { output.setUint8( fOffset, 3 ); fOffset += 1; @@ -359,7 +355,7 @@ PLYExporter.prototype = { } else { - for ( var i = 0, l = vertices.count; i < l; i += 3 ) { + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { output.setUint8( fOffset, 3 ); fOffset += 1; @@ -392,22 +388,22 @@ PLYExporter.prototype = { // Ascii File Generation // count the number of vertices - var writtenVertices = 0; - var vertexList = ''; - var faceList = ''; + let writtenVertices = 0; + let vertexList = ''; + let faceList = ''; traverseMeshes( function ( mesh, geometry ) { - var vertices = geometry.getAttribute( 'position' ); - var normals = geometry.getAttribute( 'normal' ); - var uvs = geometry.getAttribute( 'uv' ); - var colors = geometry.getAttribute( 'color' ); - var indices = geometry.getIndex(); + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); // form each line - for ( var i = 0, l = vertices.count; i < l; i ++ ) { + for ( let i = 0, l = vertices.count; i < l; i ++ ) { vertex.x = vertices.getX( i ); vertex.y = vertices.getY( i ); @@ -417,7 +413,7 @@ PLYExporter.prototype = { // Position information - var line = + let line = vertex.x + ' ' + vertex.y + ' ' + vertex.z; @@ -490,7 +486,7 @@ PLYExporter.prototype = { if ( indices !== null ) { - for ( var i = 0, l = indices.count; i < l; i += 3 ) { + for ( let i = 0, l = indices.count; i < l; i += 3 ) { faceList += `3 ${ indices.getX( i + 0 ) + writtenVertices }`; faceList += ` ${ indices.getX( i + 1 ) + writtenVertices }`; @@ -500,7 +496,7 @@ PLYExporter.prototype = { } else { - for ( var i = 0, l = vertices.count; i < l; i += 3 ) { + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { faceList += `3 ${ writtenVertices + i } ${ writtenVertices + i + 1 } ${ writtenVertices + i + 2 }\n`; @@ -521,10 +517,11 @@ PLYExporter.prototype = { } if ( typeof onDone === 'function' ) requestAnimationFrame( () => onDone( result ) ); + return result; } -}; +} export { PLYExporter }; diff --git a/examples/jsm/exporters/STLExporter.js b/examples/jsm/exporters/STLExporter.js index 606b17c31baff7..1ab549de46fd7d 100644 --- a/examples/jsm/exporters/STLExporter.js +++ b/examples/jsm/exporters/STLExporter.js @@ -4,35 +4,29 @@ import { /** * Usage: - * var exporter = new STLExporter(); + * const exporter = new STLExporter(); * * // second argument is a list of options - * var data = exporter.parse( mesh, { binary: true } ); + * const data = exporter.parse( mesh, { binary: true } ); * */ -var STLExporter = function () {}; +class STLExporter { -STLExporter.prototype = { + parse( scene, options = {} ) { - constructor: STLExporter, - - parse: function ( scene, options ) { - - if ( options === undefined ) options = {}; - - var binary = options.binary !== undefined ? options.binary : false; + const binary = options.binary !== undefined ? options.binary : false; // - var objects = []; - var triangles = 0; + const objects = []; + let triangles = 0; scene.traverse( function ( object ) { if ( object.isMesh ) { - var geometry = object.geometry; + const geometry = object.geometry; if ( geometry.isBufferGeometry !== true ) { @@ -40,8 +34,8 @@ STLExporter.prototype = { } - var index = geometry.index; - var positionAttribute = geometry.getAttribute( 'position' ); + const index = geometry.index; + const positionAttribute = geometry.getAttribute( 'position' ); triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 ); @@ -54,13 +48,13 @@ STLExporter.prototype = { } ); - var output; - var offset = 80; // skip header + let output; + let offset = 80; // skip header if ( binary === true ) { - var bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4; - var arrayBuffer = new ArrayBuffer( bufferLength ); + const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4; + const arrayBuffer = new ArrayBuffer( bufferLength ); output = new DataView( arrayBuffer ); output.setUint32( offset, triangles, true ); offset += 4; @@ -71,30 +65,30 @@ STLExporter.prototype = { } - var vA = new Vector3(); - var vB = new Vector3(); - var vC = new Vector3(); - var cb = new Vector3(); - var ab = new Vector3(); - var normal = new Vector3(); + const vA = new Vector3(); + const vB = new Vector3(); + const vC = new Vector3(); + const cb = new Vector3(); + const ab = new Vector3(); + const normal = new Vector3(); - for ( var i = 0, il = objects.length; i < il; i ++ ) { + for ( let i = 0, il = objects.length; i < il; i ++ ) { - var object = objects[ i ].object3d; - var geometry = objects[ i ].geometry; + const object = objects[ i ].object3d; + const geometry = objects[ i ].geometry; - var index = geometry.index; - var positionAttribute = geometry.getAttribute( 'position' ); + const index = geometry.index; + const positionAttribute = geometry.getAttribute( 'position' ); if ( index !== null ) { // indexed geometry - for ( var j = 0; j < index.count; j += 3 ) { + for ( let j = 0; j < index.count; j += 3 ) { - var a = index.getX( j + 0 ); - var b = index.getX( j + 1 ); - var c = index.getX( j + 2 ); + const a = index.getX( j + 0 ); + const b = index.getX( j + 1 ); + const c = index.getX( j + 2 ); writeFace( a, b, c, positionAttribute, object ); @@ -104,11 +98,11 @@ STLExporter.prototype = { // non-indexed geometry - for ( var j = 0; j < positionAttribute.count; j += 3 ) { + for ( let j = 0; j < positionAttribute.count; j += 3 ) { - var a = j + 0; - var b = j + 1; - var c = j + 2; + const a = j + 0; + const b = j + 1; + const c = j + 2; writeFace( a, b, c, positionAttribute, object ); @@ -204,6 +198,6 @@ STLExporter.prototype = { } -}; +} export { STLExporter };