Browse Source

Examples: Convert exporters to ES6. (#21605)

Michael Herzog 4 years ago
parent
commit
5069d47624

+ 82 - 84
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 = `<source id="${name}">` + `<float_array id="${name}-array" count="${array.length}">` + array.join( ' ' ) + '</float_array>' + '<technique_common>' + `<accessor source="#${name}-array" count="${Math.floor( array.length / attr.itemSize )}" stride="${attr.itemSize}">` + params.map( n => `<param name="${n}" type="${type}" />` ).join( '' ) + '</accessor>' + '</technique_common>' + '</source>';
+				const array = attrBufferToArray( attr );
+				const res = `<source id="${name}">` + `<float_array id="${name}-array" count="${array.length}">` + array.join( ' ' ) + '</float_array>' + '<technique_common>' + `<accessor source="#${name}-array" count="${Math.floor( array.length / attr.itemSize )}" stride="${attr.itemSize}">` + params.map( n => `<param name="${n}" type="${type}" />` ).join( '' ) + '</accessor>' + '</technique_common>' + '</source>';
 				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 = `<geometry id="${meshid}"${gname}><mesh>`; // define the geometry node and the vertices for the geometry
+					const gname = g.name ? ` name="${g.name}"` : '';
+					let gnode = `<geometry id="${meshid}"${gname}><mesh>`; // 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 += `<vertices id="${vertName}"><input semantic="POSITION" source="#${posName}" /></vertices>`; // 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 = `<input semantic="VERTEX" source="#${vertName}" offset="0" />`;
+					let triangleInputs = `<input semantic="VERTEX" source="#${vertName}" offset="0" />`;
 
 					if ( 'normal' in bufferGeometry.attributes ) {
 
-						var normName = `${meshid}-normal`;
+						const normName = `${meshid}-normal`;
 						gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' );
 						triangleInputs += `<input semantic="NORMAL" source="#${normName}" offset="0" />`;
 
@@ -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 += `<input semantic="TEXCOORD" source="#${uvName}" offset="0" set="0" />`;
 
@@ -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 += `<input semantic="TEXCOORD" source="#${uvName}" offset="0" set="1" />`;
 
@@ -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 += `<input semantic="COLOR" source="#${colName}" offset="0" />`;
 
 					}
 
-					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 += `<triangles material="MESH_MATERIAL_${group.materialIndex}" count="${polycount}">`;
 						gnode += triangleInputs;
 						gnode += `<p>${subarr.join( ' ' )}</p>`;
@@ -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 = `<image id="${texid}" name="${name}">`;
+					const ext = 'png';
+					const name = tex.name || texid;
+					let imageNode = `<image id="${texid}" name="${name}">`;
 
 					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 = `<technique sid="common"><${type}>` + '<emission>' + ( m.emissiveMap ? '<texture texture="emissive-sampler" texcoord="TEXCOORD" />' : `<color sid="emission">${emissive.r} ${emissive.g} ${emissive.b} 1</color>` ) + '</emission>' + ( type !== 'constant' ? '<diffuse>' + ( m.map ? '<texture texture="diffuse-sampler" texcoord="TEXCOORD" />' : `<color sid="diffuse">${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color>` ) + '</diffuse>' : '' ) + ( type !== 'constant' ? '<bump>' + ( m.normalMap ? '<texture texture="bump-sampler" texcoord="TEXCOORD" />' : '' ) + '</bump>' : '' ) + ( type === 'phong' ? `<specular><color sid="specular">${specular.r} ${specular.g} ${specular.b} 1</color></specular>` + '<shininess>' + ( m.specularMap ? '<texture texture="specular-sampler" texcoord="TEXCOORD" />' : `<float sid="shininess">${shininess}</float>` ) + '</shininess>' : '' ) + `<reflective><color>${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color></reflective>` + `<reflectivity><float>${reflectivity}</float></reflectivity>` + transparencyNode + `</${type}></technique>`;
-					var effectnode = `<effect id="${matid}-effect">` + '<profile_COMMON>' + ( m.map ? '<newparam sid="diffuse-surface"><surface type="2D">' + `<init_from>${processTexture( m.map )}</init_from>` + '</surface></newparam>' + '<newparam sid="diffuse-sampler"><sampler2D><source>diffuse-surface</source></sampler2D></newparam>' : '' ) + ( m.specularMap ? '<newparam sid="specular-surface"><surface type="2D">' + `<init_from>${processTexture( m.specularMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="specular-sampler"><sampler2D><source>specular-surface</source></sampler2D></newparam>' : '' ) + ( m.emissiveMap ? '<newparam sid="emissive-surface"><surface type="2D">' + `<init_from>${processTexture( m.emissiveMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="emissive-sampler"><sampler2D><source>emissive-surface</source></sampler2D></newparam>' : '' ) + ( m.normalMap ? '<newparam sid="bump-surface"><surface type="2D">' + `<init_from>${processTexture( m.normalMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="bump-sampler"><sampler2D><source>bump-surface</source></sampler2D></newparam>' : '' ) + techniqueNode + ( m.side === THREE.DoubleSide ? '<extra><technique profile="THREEJS"><double_sided sid="double_sided" type="int">1</double_sided></technique></extra>' : '' ) + '</profile_COMMON>' + '</effect>';
-					var materialName = m.name ? ` name="${m.name}"` : '';
-					var materialNode = `<material id="${matid}"${materialName}><instance_effect url="#${matid}-effect" /></material>`;
+					const techniqueNode = `<technique sid="common"><${type}>` + '<emission>' + ( m.emissiveMap ? '<texture texture="emissive-sampler" texcoord="TEXCOORD" />' : `<color sid="emission">${emissive.r} ${emissive.g} ${emissive.b} 1</color>` ) + '</emission>' + ( type !== 'constant' ? '<diffuse>' + ( m.map ? '<texture texture="diffuse-sampler" texcoord="TEXCOORD" />' : `<color sid="diffuse">${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color>` ) + '</diffuse>' : '' ) + ( type !== 'constant' ? '<bump>' + ( m.normalMap ? '<texture texture="bump-sampler" texcoord="TEXCOORD" />' : '' ) + '</bump>' : '' ) + ( type === 'phong' ? `<specular><color sid="specular">${specular.r} ${specular.g} ${specular.b} 1</color></specular>` + '<shininess>' + ( m.specularMap ? '<texture texture="specular-sampler" texcoord="TEXCOORD" />' : `<float sid="shininess">${shininess}</float>` ) + '</shininess>' : '' ) + `<reflective><color>${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color></reflective>` + `<reflectivity><float>${reflectivity}</float></reflectivity>` + transparencyNode + `</${type}></technique>`;
+					const effectnode = `<effect id="${matid}-effect">` + '<profile_COMMON>' + ( m.map ? '<newparam sid="diffuse-surface"><surface type="2D">' + `<init_from>${processTexture( m.map )}</init_from>` + '</surface></newparam>' + '<newparam sid="diffuse-sampler"><sampler2D><source>diffuse-surface</source></sampler2D></newparam>' : '' ) + ( m.specularMap ? '<newparam sid="specular-surface"><surface type="2D">' + `<init_from>${processTexture( m.specularMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="specular-sampler"><sampler2D><source>specular-surface</source></sampler2D></newparam>' : '' ) + ( m.emissiveMap ? '<newparam sid="emissive-surface"><surface type="2D">' + `<init_from>${processTexture( m.emissiveMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="emissive-sampler"><sampler2D><source>emissive-surface</source></sampler2D></newparam>' : '' ) + ( m.normalMap ? '<newparam sid="bump-surface"><surface type="2D">' + `<init_from>${processTexture( m.normalMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="bump-sampler"><sampler2D><source>bump-surface</source></sampler2D></newparam>' : '' ) + techniqueNode + ( m.side === THREE.DoubleSide ? '<extra><technique profile="THREEJS"><double_sided sid="double_sided" type="int">1</double_sided></technique></extra>' : '' ) + '</profile_COMMON>' + '</effect>';
+					const materialName = m.name ? ` name="${m.name}"` : '';
+					const materialNode = `<material id="${matid}"${materialName}><instance_effect url="#${matid}-effect" /></material>`;
 					libraryMaterials.push( materialNode );
 					libraryEffects.push( effectnode );
 					materialMap.set( m, matid );
@@ -390,24 +387,24 @@
 
 			function processObject( o ) {
 
-				var node = `<node name="${o.name}">`;
+				let node = `<node name="${o.name}">`;
 				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 = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' + `<COLLADA xmlns="${specLink}" version="${version}">` + '<asset>' + ( '<contributor>' + '<authoring_tool>three.js Collada Exporter</authoring_tool>' + ( options.author !== null ? `<author>${options.author}</author>` : '' ) + '</contributor>' + `<created>${new Date().toISOString()}</created>` + `<modified>${new Date().toISOString()}</modified>` + '<up_axis>Y_UP</up_axis>' ) + '</asset>';
+			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 = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' + `<COLLADA xmlns="${specLink}" version="${version}">` + '<asset>' + ( '<contributor>' + '<authoring_tool>three.js Collada Exporter</authoring_tool>' + ( options.author !== null ? `<author>${options.author}</author>` : '' ) + '</contributor>' + `<created>${new Date().toISOString()}</created>` + `<modified>${new Date().toISOString()}</modified>` + '<up_axis>Y_UP</up_axis>' ) + '</asset>';
 			dae += `<library_images>${libraryImages.join( '' )}</library_images>`;
 			dae += `<library_effects>${libraryEffects.join( '' )}</library_effects>`;
 			dae += `<library_materials>${libraryMaterials.join( '' )}</library_materials>`;
@@ -448,7 +445,7 @@
 			dae += `<library_visual_scenes><visual_scene id="Scene" name="scene">${libraryVisualScenes}</visual_scene></library_visual_scenes>`;
 			dae += '<scene><instance_visual_scene url="#Scene"/></scene>';
 			dae += '</COLLADA>';
-			var res = {
+			const res = {
 				data: format( dae ),
 				textures
 			};
@@ -462,7 +459,8 @@
 			return res;
 
 		}
-	};
+
+	}
 
 	THREE.ColladaExporter = ColladaExporter;
 

+ 35 - 41
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

+ 1421 - 1392
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<Integer>}
-		 */
-			processBufferViewImage: function ( blob ) {
+		}
+		/**
+	 * Process and generate a BufferView from an image Blob.
+	 * @param {Blob} blob
+	 * @return {Promise<Integer>}
+	 */
 
-				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<THREE.Object3D>} input
-		 */
-			processInput: function ( input ) {
+		}
+		/**
+	 * @param {THREE.Object3D|Array<THREE.Object3D>} 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;
 

+ 90 - 90
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;
 

+ 54 - 60
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;
 

+ 54 - 55
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;
 

+ 35 - 37
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;
 

+ 87 - 87
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 =
 					`<source id="${ name }">` +
 
 					`<float_array id="${ name }-array" count="${ array.length }">` +
@@ -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 = `<geometry id="${ meshid }"${ gname }><mesh>`;
+				const gname = g.name ? ` name="${ g.name }"` : '';
+				let gnode = `<geometry id="${ meshid }"${ gname }><mesh>`;
 
 				// 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 += `<vertices id="${ vertName }"><input semantic="POSITION" source="#${ posName }" /></vertices>`;
 
@@ -243,10 +242,10 @@ ColladaExporter.prototype = {
 				// MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/
 
 				// serialize normals
-				var triangleInputs = `<input semantic="VERTEX" source="#${ vertName }" offset="0" />`;
+				let triangleInputs = `<input semantic="VERTEX" source="#${ vertName }" offset="0" />`;
 				if ( 'normal' in bufferGeometry.attributes ) {
 
-					var normName = `${ meshid }-normal`;
+					const normName = `${ meshid }-normal`;
 					gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' );
 					triangleInputs += `<input semantic="NORMAL" source="#${ normName }" offset="0" />`;
 
@@ -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 += `<input semantic="TEXCOORD" source="#${ uvName }" offset="0" set="0" />`;
 
@@ -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 += `<input semantic="TEXCOORD" source="#${ uvName }" offset="0" set="1" />`;
 
@@ -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 += `<input semantic="COLOR" source="#${ colName }" offset="0" />`;
 
 				}
 
-				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 += `<triangles material="MESH_MATERIAL_${ group.materialIndex }" count="${ polycount }">`;
 					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 = `<image id="${ texid }" name="${ name }">`;
+				const ext = 'png';
+				const name = tex.name || texid;
+				let imageNode = `<image id="${ texid }" name="${ name }">`;
 
 				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 = `<technique sid="common"><${ type }>` +
+				const techniqueNode = `<technique sid="common"><${ type }>` +
 
 					'<emission>' +
 
@@ -479,7 +478,7 @@ ColladaExporter.prototype = {
 
 					`</${ type }></technique>`;
 
-				var effectnode =
+				const effectnode =
 					`<effect id="${ matid }-effect">` +
 					'<profile_COMMON>' +
 
@@ -531,8 +530,8 @@ ColladaExporter.prototype = {
 
 					'</effect>';
 
-				var materialName = m.name ? ` name="${ m.name }"` : '';
-				var materialNode = `<material id="${ matid }"${ materialName }><instance_effect url="#${ matid }-effect" /></material>`;
+				const materialName = m.name ? ` name="${ m.name }"` : '';
+				const materialNode = `<material id="${ matid }"${ materialName }><instance_effect url="#${ matid }-effect" /></material>`;
 
 				libraryMaterials.push( materialNode );
 				libraryEffects.push( effectnode );
@@ -547,7 +546,7 @@ ColladaExporter.prototype = {
 		// Recursively process the object into a scene
 		function processObject( o ) {
 
-			var node = `<node name="${ o.name }">`;
+			let node = `<node name="${ o.name }">`;
 
 			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 =
 			'<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' +
 			`<COLLADA xmlns="${ specLink }" version="${ version }">` +
 			'<asset>' +
@@ -652,7 +651,7 @@ ColladaExporter.prototype = {
 
 		dae += '</COLLADA>';
 
-		var res = {
+		const res = {
 			data: format( dae ),
 			textures
 		};
@@ -667,6 +666,7 @@ ColladaExporter.prototype = {
 
 	}
 
-};
+}
+
 
 export { ColladaExporter };

+ 33 - 45
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
 

+ 1440 - 1457
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<Integer>}
-		 */
-		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<Integer>}
+	 */
+	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<THREE.Object3D>} input
-		 */
-		processInput: function ( input ) {
+	}
 
-			var options = this.options;
+	/**
+	 * @param {THREE.Object3D|Array<THREE.Object3D>} 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 };

+ 92 - 92
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 };

+ 49 - 53
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 };

+ 54 - 57
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 };

+ 34 - 40
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 };