瀏覽代碼

Examples: Convert effects to ES6. (#21610)

Michael Herzog 4 年之前
父節點
當前提交
5f84432650

+ 66 - 64
examples/js/effects/AnaglyphEffect.js

@@ -1,94 +1,96 @@
 ( function () {
 
-	var AnaglyphEffect = function ( renderer, width, height ) {
+	class AnaglyphEffect {
 
-		// Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4
-		this.colorMatrixLeft = new THREE.Matrix3().fromArray( [ 0.456100, - 0.0400822, - 0.0152161, 0.500484, - 0.0378246, - 0.0205971, 0.176381, - 0.0157589, - 0.00546856 ] );
-		this.colorMatrixRight = new THREE.Matrix3().fromArray( [ - 0.0434706, 0.378476, - 0.0721527, - 0.0879388, 0.73364, - 0.112961, - 0.00155529, - 0.0184503, 1.2264 ] );
+		constructor( renderer, width = 512, height = 512 ) {
 
-		var _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+			// Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4
+			this.colorMatrixLeft = new THREE.Matrix3().fromArray( [ 0.456100, - 0.0400822, - 0.0152161, 0.500484, - 0.0378246, - 0.0205971, 0.176381, - 0.0157589, - 0.00546856 ] );
+			this.colorMatrixRight = new THREE.Matrix3().fromArray( [ - 0.0434706, 0.378476, - 0.0721527, - 0.0879388, 0.73364, - 0.112961, - 0.00155529, - 0.0184503, 1.2264 ] );
 
-		var _scene = new THREE.Scene();
+			const _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
 
-		var _stereo = new THREE.StereoCamera();
+			const _scene = new THREE.Scene();
 
-		var _params = {
-			minFilter: THREE.LinearFilter,
-			magFilter: THREE.NearestFilter,
-			format: THREE.RGBAFormat
-		};
-		if ( width === undefined ) width = 512;
-		if ( height === undefined ) height = 512;
+			const _stereo = new THREE.StereoCamera();
 
-		var _renderTargetL = new THREE.WebGLRenderTarget( width, height, _params );
+			const _params = {
+				minFilter: THREE.LinearFilter,
+				magFilter: THREE.NearestFilter,
+				format: THREE.RGBAFormat
+			};
 
-		var _renderTargetR = new THREE.WebGLRenderTarget( width, height, _params );
+			const _renderTargetL = new THREE.WebGLRenderTarget( width, height, _params );
 
-		var _material = new THREE.ShaderMaterial( {
-			uniforms: {
-				'mapLeft': {
-					value: _renderTargetL.texture
-				},
-				'mapRight': {
-					value: _renderTargetR.texture
-				},
-				'colorMatrixLeft': {
-					value: this.colorMatrixLeft
+			const _renderTargetR = new THREE.WebGLRenderTarget( width, height, _params );
+
+			const _material = new THREE.ShaderMaterial( {
+				uniforms: {
+					'mapLeft': {
+						value: _renderTargetL.texture
+					},
+					'mapRight': {
+						value: _renderTargetR.texture
+					},
+					'colorMatrixLeft': {
+						value: this.colorMatrixLeft
+					},
+					'colorMatrixRight': {
+						value: this.colorMatrixRight
+					}
 				},
-				'colorMatrixRight': {
-					value: this.colorMatrixRight
-				}
-			},
-			vertexShader: [ 'varying vec2 vUv;', 'void main() {', '	vUv = vec2( uv.x, uv.y );', '	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', '}' ].join( '\n' ),
-			fragmentShader: [ 'uniform sampler2D mapLeft;', 'uniform sampler2D mapRight;', 'varying vec2 vUv;', 'uniform mat3 colorMatrixLeft;', 'uniform mat3 colorMatrixRight;', // These functions implement sRGB linearization and gamma correction
-				'float lin( float c ) {', '	return c <= 0.04045 ? c * 0.0773993808 :', '			pow( c * 0.9478672986 + 0.0521327014, 2.4 );', '}', 'vec4 lin( vec4 c ) {', '	return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );', '}', 'float dev( float c ) {', '	return c <= 0.0031308 ? c * 12.92', '			: pow( c, 0.41666 ) * 1.055 - 0.055;', '}', 'void main() {', '	vec2 uv = vUv;', '	vec4 colorL = lin( texture2D( mapLeft, uv ) );', '	vec4 colorR = lin( texture2D( mapRight, uv ) );', '	vec3 color = clamp(', '			colorMatrixLeft * colorL.rgb +', '			colorMatrixRight * colorR.rgb, 0., 1. );', '	gl_FragColor = vec4(', '			dev( color.r ), dev( color.g ), dev( color.b ),', '			max( colorL.a, colorR.a ) );', '}' ].join( '\n' )
-		} );
+				vertexShader: [ 'varying vec2 vUv;', 'void main() {', '	vUv = vec2( uv.x, uv.y );', '	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', '}' ].join( '\n' ),
+				fragmentShader: [ 'uniform sampler2D mapLeft;', 'uniform sampler2D mapRight;', 'varying vec2 vUv;', 'uniform mat3 colorMatrixLeft;', 'uniform mat3 colorMatrixRight;', // These functions implement sRGB linearization and gamma correction
+					'float lin( float c ) {', '	return c <= 0.04045 ? c * 0.0773993808 :', '			pow( c * 0.9478672986 + 0.0521327014, 2.4 );', '}', 'vec4 lin( vec4 c ) {', '	return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );', '}', 'float dev( float c ) {', '	return c <= 0.0031308 ? c * 12.92', '			: pow( c, 0.41666 ) * 1.055 - 0.055;', '}', 'void main() {', '	vec2 uv = vUv;', '	vec4 colorL = lin( texture2D( mapLeft, uv ) );', '	vec4 colorR = lin( texture2D( mapRight, uv ) );', '	vec3 color = clamp(', '			colorMatrixLeft * colorL.rgb +', '			colorMatrixRight * colorR.rgb, 0., 1. );', '	gl_FragColor = vec4(', '			dev( color.r ), dev( color.g ), dev( color.b ),', '			max( colorL.a, colorR.a ) );', '}' ].join( '\n' )
+			} );
+
+			const _mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), _material );
 
-		var _mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), _material );
+			_scene.add( _mesh );
 
-		_scene.add( _mesh );
+			this.setSize = function ( width, height ) {
 
-		this.setSize = function ( width, height ) {
+				renderer.setSize( width, height );
+				const pixelRatio = renderer.getPixelRatio();
 
-			renderer.setSize( width, height );
-			var pixelRatio = renderer.getPixelRatio();
+				_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
 
-			_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
+				_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
 
-			_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
+			};
 
-		};
+			this.render = function ( scene, camera ) {
 
-		this.render = function ( scene, camera ) {
+				const currentRenderTarget = renderer.getRenderTarget();
+				scene.updateMatrixWorld();
+				if ( camera.parent === null ) camera.updateMatrixWorld();
 
-			var currentRenderTarget = renderer.getRenderTarget();
-			scene.updateMatrixWorld();
-			if ( camera.parent === null ) camera.updateMatrixWorld();
+				_stereo.update( camera );
 
-			_stereo.update( camera );
+				renderer.setRenderTarget( _renderTargetL );
+				renderer.clear();
+				renderer.render( scene, _stereo.cameraL );
+				renderer.setRenderTarget( _renderTargetR );
+				renderer.clear();
+				renderer.render( scene, _stereo.cameraR );
+				renderer.setRenderTarget( null );
+				renderer.render( _scene, _camera );
+				renderer.setRenderTarget( currentRenderTarget );
 
-			renderer.setRenderTarget( _renderTargetL );
-			renderer.clear();
-			renderer.render( scene, _stereo.cameraL );
-			renderer.setRenderTarget( _renderTargetR );
-			renderer.clear();
-			renderer.render( scene, _stereo.cameraR );
-			renderer.setRenderTarget( null );
-			renderer.render( _scene, _camera );
-			renderer.setRenderTarget( currentRenderTarget );
+			};
 
-		};
+			this.dispose = function () {
 
-		this.dispose = function () {
+				if ( _renderTargetL ) _renderTargetL.dispose();
+				if ( _renderTargetR ) _renderTargetR.dispose();
+				if ( _mesh ) _mesh.geometry.dispose();
+				if ( _material ) _material.dispose();
 
-			if ( _renderTargetL ) _renderTargetL.dispose();
-			if ( _renderTargetR ) _renderTargetR.dispose();
-			if ( _mesh ) _mesh.geometry.dispose();
-			if ( _material ) _material.dispose();
+			};
 
-		};
+		}
 
-	};
+	}
 
 	THREE.AnaglyphEffect = AnaglyphEffect;
 

+ 179 - 178
examples/js/effects/AsciiEffect.js

@@ -6,278 +6,279 @@
  *
  * 16 April 2012 - @blurspline
  */
-	var AsciiEffect = function ( renderer, charSet, options ) {
+	class AsciiEffect {
 
-		// its fun to create one your own!
-		charSet = charSet === undefined ? ' .:-=+*#%@' : charSet; // ' .,:;=|iI+hHOE#`$';
-		// darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/
-		// ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split('');
+		constructor( renderer, charSet = ' .:-=+*#%@', options = {} ) {
 
-		if ( ! options ) options = {}; // Some ASCII settings
+			// ' .,:;=|iI+hHOE#`$';
+			// darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/
+			// ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split('');
+			// Some ASCII settings
+			const bResolution = ! options[ 'resolution' ] ? 0.15 : options[ 'resolution' ]; // Higher for more details
 
-		var bResolution = ! options[ 'resolution' ] ? 0.15 : options[ 'resolution' ]; // Higher for more details
+			const iScale = ! options[ 'scale' ] ? 1 : options[ 'scale' ];
+			const bColor = ! options[ 'color' ] ? false : options[ 'color' ]; // nice but slows down rendering!
 
-		var iScale = ! options[ 'scale' ] ? 1 : options[ 'scale' ];
-		var bColor = ! options[ 'color' ] ? false : options[ 'color' ]; // nice but slows down rendering!
+			const bAlpha = ! options[ 'alpha' ] ? false : options[ 'alpha' ]; // Transparency
 
-		var bAlpha = ! options[ 'alpha' ] ? false : options[ 'alpha' ]; // Transparency
+			const bBlock = ! options[ 'block' ] ? false : options[ 'block' ]; // blocked characters. like good O dos
 
-		var bBlock = ! options[ 'block' ] ? false : options[ 'block' ]; // blocked characters. like good O dos
+			const bInvert = ! options[ 'invert' ] ? false : options[ 'invert' ]; // black is white, white is black
 
-		var bInvert = ! options[ 'invert' ] ? false : options[ 'invert' ]; // black is white, white is black
+			const strResolution = 'low';
+			let width, height;
+			const domElement = document.createElement( 'div' );
+			domElement.style.cursor = 'default';
+			const oAscii = document.createElement( 'table' );
+			domElement.appendChild( oAscii );
+			let iWidth, iHeight;
+			let oImg;
 
-		var strResolution = 'low';
-		var width, height;
-		var domElement = document.createElement( 'div' );
-		domElement.style.cursor = 'default';
-		var oAscii = document.createElement( 'table' );
-		domElement.appendChild( oAscii );
-		var iWidth, iHeight;
-		var oImg;
+			this.setSize = function ( w, h ) {
 
-		this.setSize = function ( w, h ) {
+				width = w;
+				height = h;
+				renderer.setSize( w, h );
+				initAsciiSize();
 
-			width = w;
-			height = h;
-			renderer.setSize( w, h );
-			initAsciiSize();
+			};
 
-		};
+			this.render = function ( scene, camera ) {
 
-		this.render = function ( scene, camera ) {
+				renderer.render( scene, camera );
+				asciifyImage( renderer, oAscii );
 
-			renderer.render( scene, camera );
-			asciifyImage( renderer, oAscii );
+			};
 
-		};
+			this.domElement = domElement; // Throw in ascii library from http://www.nihilogic.dk/labs/jsascii/jsascii.js
 
-		this.domElement = domElement; // Throw in ascii library from http://www.nihilogic.dk/labs/jsascii/jsascii.js
+			/*
+		* jsAscii 0.1
+		* Copyright (c) 2008 Jacob Seidelin, [email protected], http://blog.nihilogic.dk/
+		* MIT License [http://www.nihilogic.dk/licenses/mit-license.txt]
+		*/
 
-		/*
-	* jsAscii 0.1
-	* Copyright (c) 2008 Jacob Seidelin, [email protected], http://blog.nihilogic.dk/
-	* MIT License [http://www.nihilogic.dk/licenses/mit-license.txt]
-	*/
+			function initAsciiSize() {
 
-		function initAsciiSize() {
+				iWidth = Math.round( width * fResolution );
+				iHeight = Math.round( height * fResolution );
+				oCanvas.width = iWidth;
+				oCanvas.height = iHeight; // oCanvas.style.display = "none";
+				// oCanvas.style.width = iWidth;
+				// oCanvas.style.height = iHeight;
 
-			iWidth = Math.round( width * fResolution );
-			iHeight = Math.round( height * fResolution );
-			oCanvas.width = iWidth;
-			oCanvas.height = iHeight; // oCanvas.style.display = "none";
-			// oCanvas.style.width = iWidth;
-			// oCanvas.style.height = iHeight;
+				oImg = renderer.domElement;
 
-			oImg = renderer.domElement;
+				if ( oImg.style.backgroundColor ) {
 
-			if ( oImg.style.backgroundColor ) {
+					oAscii.rows[ 0 ].cells[ 0 ].style.backgroundColor = oImg.style.backgroundColor;
+					oAscii.rows[ 0 ].cells[ 0 ].style.color = oImg.style.color;
 
-				oAscii.rows[ 0 ].cells[ 0 ].style.backgroundColor = oImg.style.backgroundColor;
-				oAscii.rows[ 0 ].cells[ 0 ].style.color = oImg.style.color;
+				}
+
+				oAscii.cellSpacing = 0;
+				oAscii.cellPadding = 0;
+				const oStyle = oAscii.style;
+				oStyle.display = 'inline';
+				oStyle.width = Math.round( iWidth / fResolution * iScale ) + 'px';
+				oStyle.height = Math.round( iHeight / fResolution * iScale ) + 'px';
+				oStyle.whiteSpace = 'pre';
+				oStyle.margin = '0px';
+				oStyle.padding = '0px';
+				oStyle.letterSpacing = fLetterSpacing + 'px';
+				oStyle.fontFamily = strFont;
+				oStyle.fontSize = fFontSize + 'px';
+				oStyle.lineHeight = fLineHeight + 'px';
+				oStyle.textAlign = 'left';
+				oStyle.textDecoration = 'none';
 
 			}
 
-			oAscii.cellSpacing = 0;
-			oAscii.cellPadding = 0;
-			var oStyle = oAscii.style;
-			oStyle.display = 'inline';
-			oStyle.width = Math.round( iWidth / fResolution * iScale ) + 'px';
-			oStyle.height = Math.round( iHeight / fResolution * iScale ) + 'px';
-			oStyle.whiteSpace = 'pre';
-			oStyle.margin = '0px';
-			oStyle.padding = '0px';
-			oStyle.letterSpacing = fLetterSpacing + 'px';
-			oStyle.fontFamily = strFont;
-			oStyle.fontSize = fFontSize + 'px';
-			oStyle.lineHeight = fLineHeight + 'px';
-			oStyle.textAlign = 'left';
-			oStyle.textDecoration = 'none';
+			const aDefaultCharList = ' .,:;i1tfLCG08@'.split( '' );
+			const aDefaultColorCharList = ' CGO08@'.split( '' );
+			const strFont = 'courier new, monospace';
+			const oCanvasImg = renderer.domElement;
+			const oCanvas = document.createElement( 'canvas' );
 
-		}
+			if ( ! oCanvas.getContext ) {
 
-		var aDefaultCharList = ' .,:;i1tfLCG08@'.split( '' );
-		var aDefaultColorCharList = ' CGO08@'.split( '' );
-		var strFont = 'courier new, monospace';
-		var oCanvasImg = renderer.domElement;
-		var oCanvas = document.createElement( 'canvas' );
+				return;
 
-		if ( ! oCanvas.getContext ) {
+			}
 
-			return;
+			const oCtx = oCanvas.getContext( '2d' );
 
-		}
+			if ( ! oCtx.getImageData ) {
 
-		var oCtx = oCanvas.getContext( '2d' );
+				return;
 
-		if ( ! oCtx.getImageData ) {
-
-			return;
+			}
 
-		}
+			let aCharList = bColor ? aDefaultColorCharList : aDefaultCharList;
+			if ( charSet ) aCharList = charSet;
+			let fResolution = 0.5;
 
-		var aCharList = bColor ? aDefaultColorCharList : aDefaultCharList;
-		if ( charSet ) aCharList = charSet;
-		var fResolution = 0.5;
+			switch ( strResolution ) {
 
-		switch ( strResolution ) {
+				case 'low':
+					fResolution = 0.25;
+					break;
 
-			case 'low':
-				fResolution = 0.25;
-				break;
+				case 'medium':
+					fResolution = 0.5;
+					break;
 
-			case 'medium':
-				fResolution = 0.5;
-				break;
+				case 'high':
+					fResolution = 1;
+					break;
 
-			case 'high':
-				fResolution = 1;
-				break;
+			}
 
-		}
+			if ( bResolution ) fResolution = bResolution; // Setup dom
 
-		if ( bResolution ) fResolution = bResolution; // Setup dom
+			const fFontSize = 2 / fResolution * iScale;
+			const fLineHeight = 2 / fResolution * iScale; // adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width.
 
-		var fFontSize = 2 / fResolution * iScale;
-		var fLineHeight = 2 / fResolution * iScale; // adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width.
+			let fLetterSpacing = 0;
 
-		var fLetterSpacing = 0;
+			if ( strResolution == 'low' ) {
 
-		if ( strResolution == 'low' ) {
+				switch ( iScale ) {
 
-			switch ( iScale ) {
+					case 1:
+						fLetterSpacing = - 1;
+						break;
 
-				case 1:
-					fLetterSpacing = - 1;
-					break;
+					case 2:
+					case 3:
+						fLetterSpacing = - 2.1;
+						break;
 
-				case 2:
-				case 3:
-					fLetterSpacing = - 2.1;
-					break;
+					case 4:
+						fLetterSpacing = - 3.1;
+						break;
 
-				case 4:
-					fLetterSpacing = - 3.1;
-					break;
+					case 5:
+						fLetterSpacing = - 4.15;
+						break;
 
-				case 5:
-					fLetterSpacing = - 4.15;
-					break;
+				}
 
 			}
 
-		}
+			if ( strResolution == 'medium' ) {
 
-		if ( strResolution == 'medium' ) {
+				switch ( iScale ) {
 
-			switch ( iScale ) {
+					case 1:
+						fLetterSpacing = 0;
+						break;
 
-				case 1:
-					fLetterSpacing = 0;
-					break;
+					case 2:
+						fLetterSpacing = - 1;
+						break;
 
-				case 2:
-					fLetterSpacing = - 1;
-					break;
+					case 3:
+						fLetterSpacing = - 1.04;
+						break;
 
-				case 3:
-					fLetterSpacing = - 1.04;
-					break;
+					case 4:
+					case 5:
+						fLetterSpacing = - 2.1;
+						break;
 
-				case 4:
-				case 5:
-					fLetterSpacing = - 2.1;
-					break;
+				}
 
 			}
 
-		}
+			if ( strResolution == 'high' ) {
 
-		if ( strResolution == 'high' ) {
+				switch ( iScale ) {
 
-			switch ( iScale ) {
+					case 1:
+					case 2:
+						fLetterSpacing = 0;
+						break;
 
-				case 1:
-				case 2:
-					fLetterSpacing = 0;
-					break;
+					case 3:
+					case 4:
+					case 5:
+						fLetterSpacing = - 1;
+						break;
 
-				case 3:
-				case 4:
-				case 5:
-					fLetterSpacing = - 1;
-					break;
+				}
 
-			}
+			} // can't get a span or div to flow like an img element, but a table works?
+			// convert img element to ascii
 
-		} // can't get a span or div to flow like an img element, but a table works?
-		// convert img element to ascii
 
+			function asciifyImage( canvasRenderer, oAscii ) {
 
-		function asciifyImage( canvasRenderer, oAscii ) {
+				oCtx.clearRect( 0, 0, iWidth, iHeight );
+				oCtx.drawImage( oCanvasImg, 0, 0, iWidth, iHeight );
+				const oImgData = oCtx.getImageData( 0, 0, iWidth, iHeight ).data; // Coloring loop starts now
 
-			oCtx.clearRect( 0, 0, iWidth, iHeight );
-			oCtx.drawImage( oCanvasImg, 0, 0, iWidth, iHeight );
-			var oImgData = oCtx.getImageData( 0, 0, iWidth, iHeight ).data; // Coloring loop starts now
+				let strChars = ''; // console.time('rendering');
 
-			var strChars = ''; // console.time('rendering');
+				for ( let y = 0; y < iHeight; y += 2 ) {
 
-			for ( var y = 0; y < iHeight; y += 2 ) {
+					for ( let x = 0; x < iWidth; x ++ ) {
 
-				for ( var x = 0; x < iWidth; x ++ ) {
+						const iOffset = ( y * iWidth + x ) * 4;
+						const iRed = oImgData[ iOffset ];
+						const iGreen = oImgData[ iOffset + 1 ];
+						const iBlue = oImgData[ iOffset + 2 ];
+						const iAlpha = oImgData[ iOffset + 3 ];
+						let iCharIdx;
+						let fBrightness;
+						fBrightness = ( 0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue ) / 255; // fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255;
 
-					var iOffset = ( y * iWidth + x ) * 4;
-					var iRed = oImgData[ iOffset ];
-					var iGreen = oImgData[ iOffset + 1 ];
-					var iBlue = oImgData[ iOffset + 2 ];
-					var iAlpha = oImgData[ iOffset + 3 ];
-					var iCharIdx;
-					var fBrightness;
-					fBrightness = ( 0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue ) / 255; // fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255;
+						if ( iAlpha == 0 ) {
 
-					if ( iAlpha == 0 ) {
+							// should calculate alpha instead, but quick hack :)
+							//fBrightness *= (iAlpha / 255);
+							fBrightness = 1;
 
-						// should calculate alpha instead, but quick hack :)
-						//fBrightness *= (iAlpha / 255);
-						fBrightness = 1;
+						}
 
-					}
+						iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) );
 
-					iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) );
+						if ( bInvert ) {
 
-					if ( bInvert ) {
+							iCharIdx = aCharList.length - iCharIdx - 1;
 
-						iCharIdx = aCharList.length - iCharIdx - 1;
+						} // good for debugging
+						//fBrightness = Math.floor(fBrightness * 10);
+						//strThisChar = fBrightness;
 
-					} // good for debugging
-					//fBrightness = Math.floor(fBrightness * 10);
-					//strThisChar = fBrightness;
 
+						let strThisChar = aCharList[ iCharIdx ];
+						if ( strThisChar === undefined || strThisChar == ' ' ) strThisChar = '&nbsp;';
 
-					var strThisChar = aCharList[ iCharIdx ];
-					if ( strThisChar === undefined || strThisChar == ' ' ) strThisChar = '&nbsp;';
+						if ( bColor ) {
 
-					if ( bColor ) {
+							strChars += '<span style=\'' + 'color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' + ( bBlock ? 'background-color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' : '' ) + ( bAlpha ? 'opacity:' + iAlpha / 255 + ';' : '' ) + '\'>' + strThisChar + '</span>';
 
-						strChars += '<span style=\'' + 'color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' + ( bBlock ? 'background-color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' : '' ) + ( bAlpha ? 'opacity:' + iAlpha / 255 + ';' : '' ) + '\'>' + strThisChar + '</span>';
+						} else {
 
-					} else {
+							strChars += strThisChar;
 
-						strChars += strThisChar;
+						}
 
 					}
 
+					strChars += '<br/>';
+
 				}
 
-				strChars += '<br/>';
+				oAscii.innerHTML = '<tr><td>' + strChars + '</td></tr>'; // console.timeEnd('rendering');
+				// return oAscii;
 
 			}
 
-			oAscii.innerHTML = '<tr><td>' + strChars + '</td></tr>'; // console.timeEnd('rendering');
-			// return oAscii;
-
-		} // end modified asciifyImage block
+		}
 
-	};
+	}
 
 	THREE.AsciiEffect = AsciiEffect;
 

+ 266 - 263
examples/js/effects/OutlineEffect.js

@@ -7,7 +7,7 @@
  *
  * 1. Traditional
  *
- * var effect = new OutlineEffect( renderer );
+ * const effect = new OutlineEffect( renderer );
  *
  * function render() {
  *
@@ -17,8 +17,8 @@
  *
  * 2. VR compatible
  *
- * var effect = new OutlineEffect( renderer );
- * var renderingOutline = false;
+ * const effect = new OutlineEffect( renderer );
+ * let renderingOutline = false;
  *
  * scene.onAfterRender = function () {
  *
@@ -56,418 +56,421 @@
  * };
  */
 
-	var OutlineEffect = function ( renderer, parameters ) {
-
-		parameters = parameters || {};
-		this.enabled = true;
-		var defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003;
-		var defaultColor = new THREE.Color().fromArray( parameters.defaultColor !== undefined ? parameters.defaultColor : [ 0, 0, 0 ] );
-		var defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0;
-		var defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false; // object.material.uuid -> outlineMaterial or
-		// object.material[ n ].uuid -> outlineMaterial
-		// save at the outline material creation and release
-		// if it's unused removeThresholdCount frames
-		// unless keepAlive is true.
-
-		var cache = {};
-		var removeThresholdCount = 60; // outlineMaterial.uuid -> object.material or
-		// outlineMaterial.uuid -> object.material[ n ]
-		// save before render and release after render.
-
-		var originalMaterials = {}; // object.uuid -> originalOnBeforeRender
-		// save before render and release after render.
-
-		var originalOnBeforeRenders = {}; //this.cache = cache;	// for debug
-
-		var uniformsOutline = {
-			outlineThickness: {
-				value: defaultThickness
-			},
-			outlineColor: {
-				value: defaultColor
-			},
-			outlineAlpha: {
-				value: defaultAlpha
+	class OutlineEffect {
+
+		constructor( renderer, parameters = {} ) {
+
+			this.enabled = true;
+			const defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003;
+			const defaultColor = new THREE.Color().fromArray( parameters.defaultColor !== undefined ? parameters.defaultColor : [ 0, 0, 0 ] );
+			const defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0;
+			const defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false; // object.material.uuid -> outlineMaterial or
+			// object.material[ n ].uuid -> outlineMaterial
+			// save at the outline material creation and release
+			// if it's unused removeThresholdCount frames
+			// unless keepAlive is true.
+
+			const cache = {};
+			const removeThresholdCount = 60; // outlineMaterial.uuid -> object.material or
+			// outlineMaterial.uuid -> object.material[ n ]
+			// save before render and release after render.
+
+			const originalMaterials = {}; // object.uuid -> originalOnBeforeRender
+			// save before render and release after render.
+
+			const originalOnBeforeRenders = {}; //this.cache = cache;	// for debug
+
+			const uniformsOutline = {
+				outlineThickness: {
+					value: defaultThickness
+				},
+				outlineColor: {
+					value: defaultColor
+				},
+				outlineAlpha: {
+					value: defaultAlpha
+				}
+			};
+			const vertexShader = [ '#include <common>', '#include <uv_pars_vertex>', '#include <displacementmap_pars_vertex>', '#include <fog_pars_vertex>', '#include <morphtarget_pars_vertex>', '#include <skinning_pars_vertex>', '#include <logdepthbuf_pars_vertex>', '#include <clipping_planes_pars_vertex>', 'uniform float outlineThickness;', 'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {', '	float thickness = outlineThickness;', '	const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex
+				'	vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );', // NOTE: subtract pos2 from pos because THREE.BackSide objectNormal is negative
+				'	vec4 norm = normalize( pos - pos2 );', '	return pos + norm * thickness * pos.w * ratio;', '}', 'void main() {', '	#include <uv_vertex>', '	#include <beginnormal_vertex>', '	#include <morphnormal_vertex>', '	#include <skinbase_vertex>', '	#include <skinnormal_vertex>', '	#include <begin_vertex>', '	#include <morphtarget_vertex>', '	#include <skinning_vertex>', '	#include <displacementmap_vertex>', '	#include <project_vertex>', '	vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with THREE.BackSide
+				'	gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );', '	#include <logdepthbuf_vertex>', '	#include <clipping_planes_vertex>', '	#include <fog_vertex>', '}' ].join( '\n' );
+			const fragmentShader = [ '#include <common>', '#include <fog_pars_fragment>', '#include <logdepthbuf_pars_fragment>', '#include <clipping_planes_pars_fragment>', 'uniform vec3 outlineColor;', 'uniform float outlineAlpha;', 'void main() {', '	#include <clipping_planes_fragment>', '	#include <logdepthbuf_fragment>', '	gl_FragColor = vec4( outlineColor, outlineAlpha );', '	#include <tonemapping_fragment>', '	#include <encodings_fragment>', '	#include <fog_fragment>', '	#include <premultiplied_alpha_fragment>', '}' ].join( '\n' );
+
+			function createMaterial() {
+
+				return new THREE.ShaderMaterial( {
+					type: 'OutlineEffect',
+					uniforms: THREE.UniformsUtils.merge( [ THREE.UniformsLib[ 'fog' ], THREE.UniformsLib[ 'displacementmap' ], uniformsOutline ] ),
+					vertexShader: vertexShader,
+					fragmentShader: fragmentShader,
+					side: THREE.BackSide
+				} );
+
 			}
-		};
-		var vertexShader = [ '#include <common>', '#include <uv_pars_vertex>', '#include <displacementmap_pars_vertex>', '#include <fog_pars_vertex>', '#include <morphtarget_pars_vertex>', '#include <skinning_pars_vertex>', '#include <logdepthbuf_pars_vertex>', '#include <clipping_planes_pars_vertex>', 'uniform float outlineThickness;', 'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {', '	float thickness = outlineThickness;', '	const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex
-			'	vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );', // NOTE: subtract pos2 from pos because THREE.BackSide objectNormal is negative
-			'	vec4 norm = normalize( pos - pos2 );', '	return pos + norm * thickness * pos.w * ratio;', '}', 'void main() {', '	#include <uv_vertex>', '	#include <beginnormal_vertex>', '	#include <morphnormal_vertex>', '	#include <skinbase_vertex>', '	#include <skinnormal_vertex>', '	#include <begin_vertex>', '	#include <morphtarget_vertex>', '	#include <skinning_vertex>', '	#include <displacementmap_vertex>', '	#include <project_vertex>', '	vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with THREE.BackSide
-			'	gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );', '	#include <logdepthbuf_vertex>', '	#include <clipping_planes_vertex>', '	#include <fog_vertex>', '}' ].join( '\n' );
-		var fragmentShader = [ '#include <common>', '#include <fog_pars_fragment>', '#include <logdepthbuf_pars_fragment>', '#include <clipping_planes_pars_fragment>', 'uniform vec3 outlineColor;', 'uniform float outlineAlpha;', 'void main() {', '	#include <clipping_planes_fragment>', '	#include <logdepthbuf_fragment>', '	gl_FragColor = vec4( outlineColor, outlineAlpha );', '	#include <tonemapping_fragment>', '	#include <encodings_fragment>', '	#include <fog_fragment>', '	#include <premultiplied_alpha_fragment>', '}' ].join( '\n' );
-
-		function createMaterial() {
-
-			return new THREE.ShaderMaterial( {
-				type: 'OutlineEffect',
-				uniforms: THREE.UniformsUtils.merge( [ THREE.UniformsLib[ 'fog' ], THREE.UniformsLib[ 'displacementmap' ], uniformsOutline ] ),
-				vertexShader: vertexShader,
-				fragmentShader: fragmentShader,
-				side: THREE.BackSide
-			} );
 
-		}
+			function getOutlineMaterialFromCache( originalMaterial ) {
 
-		function getOutlineMaterialFromCache( originalMaterial ) {
+				let data = cache[ originalMaterial.uuid ];
 
-			var data = cache[ originalMaterial.uuid ];
+				if ( data === undefined ) {
 
-			if ( data === undefined ) {
+					data = {
+						material: createMaterial(),
+						used: true,
+						keepAlive: defaultKeepAlive,
+						count: 0
+					};
+					cache[ originalMaterial.uuid ] = data;
 
-				data = {
-					material: createMaterial(),
-					used: true,
-					keepAlive: defaultKeepAlive,
-					count: 0
-				};
-				cache[ originalMaterial.uuid ] = data;
+				}
 
-			}
+				data.used = true;
+				return data.material;
 
-			data.used = true;
-			return data.material;
+			}
 
-		}
+			function getOutlineMaterial( originalMaterial ) {
 
-		function getOutlineMaterial( originalMaterial ) {
+				const outlineMaterial = getOutlineMaterialFromCache( originalMaterial );
+				originalMaterials[ outlineMaterial.uuid ] = originalMaterial;
+				updateOutlineMaterial( outlineMaterial, originalMaterial );
+				return outlineMaterial;
 
-			var outlineMaterial = getOutlineMaterialFromCache( originalMaterial );
-			originalMaterials[ outlineMaterial.uuid ] = originalMaterial;
-			updateOutlineMaterial( outlineMaterial, originalMaterial );
-			return outlineMaterial;
+			}
 
-		}
+			function isCompatible( object ) {
 
-		function isCompatible( object ) {
+				const geometry = object.geometry;
+				let hasNormals = false;
 
-			var geometry = object.geometry;
-			var hasNormals = false;
+				if ( object.geometry !== undefined ) {
 
-			if ( object.geometry !== undefined ) {
+					if ( geometry.isBufferGeometry ) {
 
-				if ( geometry.isBufferGeometry ) {
+						hasNormals = geometry.attributes.normal !== undefined;
 
-					hasNormals = geometry.attributes.normal !== undefined;
+					} else {
 
-				} else {
+						hasNormals = true; // the renderer always produces a normal attribute for Geometry
 
-					hasNormals = true; // the renderer always produces a normal attribute for Geometry
+					}
 
 				}
 
+				return object.isMesh === true && object.material !== undefined && hasNormals === true;
+
 			}
 
-			return object.isMesh === true && object.material !== undefined && hasNormals === true;
+			function setOutlineMaterial( object ) {
 
-		}
+				if ( isCompatible( object ) === false ) return;
+
+				if ( Array.isArray( object.material ) ) {
 
-		function setOutlineMaterial( object ) {
+					for ( let i = 0, il = object.material.length; i < il; i ++ ) {
 
-			if ( isCompatible( object ) === false ) return;
+						object.material[ i ] = getOutlineMaterial( object.material[ i ] );
 
-			if ( Array.isArray( object.material ) ) {
+					}
 
-				for ( var i = 0, il = object.material.length; i < il; i ++ ) {
+				} else {
 
-					object.material[ i ] = getOutlineMaterial( object.material[ i ] );
+					object.material = getOutlineMaterial( object.material );
 
 				}
 
-			} else {
-
-				object.material = getOutlineMaterial( object.material );
+				originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender;
+				object.onBeforeRender = onBeforeRender;
 
 			}
 
-			originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender;
-			object.onBeforeRender = onBeforeRender;
+			function restoreOriginalMaterial( object ) {
 
-		}
+				if ( isCompatible( object ) === false ) return;
 
-		function restoreOriginalMaterial( object ) {
+				if ( Array.isArray( object.material ) ) {
 
-			if ( isCompatible( object ) === false ) return;
+					for ( let i = 0, il = object.material.length; i < il; i ++ ) {
 
-			if ( Array.isArray( object.material ) ) {
+						object.material[ i ] = originalMaterials[ object.material[ i ].uuid ];
 
-				for ( var i = 0, il = object.material.length; i < il; i ++ ) {
+					}
 
-					object.material[ i ] = originalMaterials[ object.material[ i ].uuid ];
+				} else {
 
-				}
+					object.material = originalMaterials[ object.material.uuid ];
 
-			} else {
+				}
 
-				object.material = originalMaterials[ object.material.uuid ];
+				object.onBeforeRender = originalOnBeforeRenders[ object.uuid ];
 
 			}
 
-			object.onBeforeRender = originalOnBeforeRenders[ object.uuid ];
+			function onBeforeRender( renderer, scene, camera, geometry, material ) {
 
-		}
+				const originalMaterial = originalMaterials[ material.uuid ]; // just in case
 
-		function onBeforeRender( renderer, scene, camera, geometry, material ) {
+				if ( originalMaterial === undefined ) return;
+				updateUniforms( material, originalMaterial );
 
-			var originalMaterial = originalMaterials[ material.uuid ]; // just in case
+			}
 
-			if ( originalMaterial === undefined ) return;
-			updateUniforms( material, originalMaterial );
+			function updateUniforms( material, originalMaterial ) {
 
-		}
+				const outlineParameters = originalMaterial.userData.outlineParameters;
+				material.uniforms.outlineAlpha.value = originalMaterial.opacity;
 
-		function updateUniforms( material, originalMaterial ) {
+				if ( outlineParameters !== undefined ) {
 
-			var outlineParameters = originalMaterial.userData.outlineParameters;
-			material.uniforms.outlineAlpha.value = originalMaterial.opacity;
+					if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness;
+					if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.fromArray( outlineParameters.color );
+					if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha;
 
-			if ( outlineParameters !== undefined ) {
+				}
+
+				if ( originalMaterial.displacementMap ) {
 
-				if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness;
-				if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.fromArray( outlineParameters.color );
-				if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha;
+					material.uniforms.displacementMap.value = originalMaterial.displacementMap;
+					material.uniforms.displacementScale.value = originalMaterial.displacementScale;
+					material.uniforms.displacementBias.value = originalMaterial.displacementBias;
+
+				}
 
 			}
 
-			if ( originalMaterial.displacementMap ) {
+			function updateOutlineMaterial( material, originalMaterial ) {
 
-				material.uniforms.displacementMap.value = originalMaterial.displacementMap;
-				material.uniforms.displacementScale.value = originalMaterial.displacementScale;
-				material.uniforms.displacementBias.value = originalMaterial.displacementBias;
+				if ( material.name === 'invisible' ) return;
+				const outlineParameters = originalMaterial.userData.outlineParameters;
+				material.skinning = originalMaterial.skinning;
+				material.morphTargets = originalMaterial.morphTargets;
+				material.morphNormals = originalMaterial.morphNormals;
+				material.fog = originalMaterial.fog;
+				material.toneMapped = originalMaterial.toneMapped;
+				material.premultipliedAlpha = originalMaterial.premultipliedAlpha;
+				material.displacementMap = originalMaterial.displacementMap;
 
-			}
+				if ( outlineParameters !== undefined ) {
 
-		}
+					if ( originalMaterial.visible === false ) {
 
-		function updateOutlineMaterial( material, originalMaterial ) {
+						material.visible = false;
 
-			if ( material.name === 'invisible' ) return;
-			var outlineParameters = originalMaterial.userData.outlineParameters;
-			material.skinning = originalMaterial.skinning;
-			material.morphTargets = originalMaterial.morphTargets;
-			material.morphNormals = originalMaterial.morphNormals;
-			material.fog = originalMaterial.fog;
-			material.toneMapped = originalMaterial.toneMapped;
-			material.premultipliedAlpha = originalMaterial.premultipliedAlpha;
-			material.displacementMap = originalMaterial.displacementMap;
+					} else {
 
-			if ( outlineParameters !== undefined ) {
+						material.visible = outlineParameters.visible !== undefined ? outlineParameters.visible : true;
 
-				if ( originalMaterial.visible === false ) {
+					}
 
-					material.visible = false;
+					material.transparent = outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ? true : originalMaterial.transparent;
+					if ( outlineParameters.keepAlive !== undefined ) cache[ originalMaterial.uuid ].keepAlive = outlineParameters.keepAlive;
 
 				} else {
 
-					material.visible = outlineParameters.visible !== undefined ? outlineParameters.visible : true;
+					material.transparent = originalMaterial.transparent;
+					material.visible = originalMaterial.visible;
 
 				}
 
-				material.transparent = outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ? true : originalMaterial.transparent;
-				if ( outlineParameters.keepAlive !== undefined ) cache[ originalMaterial.uuid ].keepAlive = outlineParameters.keepAlive;
+				if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false;
 
-			} else {
-
-				material.transparent = originalMaterial.transparent;
-				material.visible = originalMaterial.visible;
-
-			}
+				if ( originalMaterial.clippingPlanes ) {
 
-			if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false;
+					material.clipping = true;
+					material.clippingPlanes = originalMaterial.clippingPlanes;
+					material.clipIntersection = originalMaterial.clipIntersection;
+					material.clipShadows = originalMaterial.clipShadows;
 
-			if ( originalMaterial.clippingPlanes ) {
+				}
 
-				material.clipping = true;
-				material.clippingPlanes = originalMaterial.clippingPlanes;
-				material.clipIntersection = originalMaterial.clipIntersection;
-				material.clipShadows = originalMaterial.clipShadows;
+				material.version = originalMaterial.version; // update outline material if necessary
 
 			}
 
-			material.version = originalMaterial.version; // update outline material if necessary
+			function cleanupCache() {
 
-		}
+				let keys; // clear originialMaterials
 
-		function cleanupCache() {
+				keys = Object.keys( originalMaterials );
 
-			var keys; // clear originialMaterials
+				for ( let i = 0, il = keys.length; i < il; i ++ ) {
 
-			keys = Object.keys( originalMaterials );
+					originalMaterials[ keys[ i ] ] = undefined;
 
-			for ( var i = 0, il = keys.length; i < il; i ++ ) {
+				} // clear originalOnBeforeRenders
 
-				originalMaterials[ keys[ i ] ] = undefined;
 
-			} // clear originalOnBeforeRenders
+				keys = Object.keys( originalOnBeforeRenders );
 
+				for ( let i = 0, il = keys.length; i < il; i ++ ) {
 
-			keys = Object.keys( originalOnBeforeRenders );
+					originalOnBeforeRenders[ keys[ i ] ] = undefined;
 
-			for ( var i = 0, il = keys.length; i < il; i ++ ) {
+				} // remove unused outlineMaterial from cache
 
-				originalOnBeforeRenders[ keys[ i ] ] = undefined;
 
-			} // remove unused outlineMaterial from cache
+				keys = Object.keys( cache );
 
+				for ( let i = 0, il = keys.length; i < il; i ++ ) {
 
-			keys = Object.keys( cache );
+					const key = keys[ i ];
 
-			for ( var i = 0, il = keys.length; i < il; i ++ ) {
+					if ( cache[ key ].used === false ) {
 
-				var key = keys[ i ];
+						cache[ key ].count ++;
 
-				if ( cache[ key ].used === false ) {
+						if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) {
 
-					cache[ key ].count ++;
+							delete cache[ key ];
 
-					if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) {
+						}
 
-						delete cache[ key ];
+					} else {
 
-					}
+						cache[ key ].used = false;
+						cache[ key ].count = 0;
 
-				} else {
-
-					cache[ key ].used = false;
-					cache[ key ].count = 0;
+					}
 
 				}
 
 			}
 
-		}
+			this.render = function ( scene, camera ) {
 
-		this.render = function ( scene, camera ) {
+				let renderTarget;
+				let forceClear = false;
 
-			var renderTarget;
-			var forceClear = false;
+				if ( arguments[ 2 ] !== undefined ) {
 
-			if ( arguments[ 2 ] !== undefined ) {
+					console.warn( 'THREE.OutlineEffect.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' );
+					renderTarget = arguments[ 2 ];
 
-				console.warn( 'THREE.OutlineEffect.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' );
-				renderTarget = arguments[ 2 ];
-
-			}
+				}
 
-			if ( arguments[ 3 ] !== undefined ) {
+				if ( arguments[ 3 ] !== undefined ) {
 
-				console.warn( 'THREE.OutlineEffect.render(): the forceClear argument has been removed. Use .clear() instead.' );
-				forceClear = arguments[ 3 ];
+					console.warn( 'THREE.OutlineEffect.render(): the forceClear argument has been removed. Use .clear() instead.' );
+					forceClear = arguments[ 3 ];
 
-			}
+				}
 
-			if ( renderTarget !== undefined ) renderer.setRenderTarget( renderTarget );
-			if ( forceClear ) renderer.clear();
+				if ( renderTarget !== undefined ) renderer.setRenderTarget( renderTarget );
+				if ( forceClear ) renderer.clear();
 
-			if ( this.enabled === false ) {
+				if ( this.enabled === false ) {
 
-				renderer.render( scene, camera );
-				return;
+					renderer.render( scene, camera );
+					return;
 
-			}
-
-			var currentAutoClear = renderer.autoClear;
-			renderer.autoClear = this.autoClear;
-			renderer.render( scene, camera );
-			renderer.autoClear = currentAutoClear;
-			this.renderOutline( scene, camera );
-
-		};
+				}
 
-		this.renderOutline = function ( scene, camera ) {
+				const currentAutoClear = renderer.autoClear;
+				renderer.autoClear = this.autoClear;
+				renderer.render( scene, camera );
+				renderer.autoClear = currentAutoClear;
+				this.renderOutline( scene, camera );
+
+			};
+
+			this.renderOutline = function ( scene, camera ) {
+
+				const currentAutoClear = renderer.autoClear;
+				const currentSceneAutoUpdate = scene.autoUpdate;
+				const currentSceneBackground = scene.background;
+				const currentShadowMapEnabled = renderer.shadowMap.enabled;
+				scene.autoUpdate = false;
+				scene.background = null;
+				renderer.autoClear = false;
+				renderer.shadowMap.enabled = false;
+				scene.traverse( setOutlineMaterial );
+				renderer.render( scene, camera );
+				scene.traverse( restoreOriginalMaterial );
+				cleanupCache();
+				scene.autoUpdate = currentSceneAutoUpdate;
+				scene.background = currentSceneBackground;
+				renderer.autoClear = currentAutoClear;
+				renderer.shadowMap.enabled = currentShadowMapEnabled;
 
-			var currentAutoClear = renderer.autoClear;
-			var currentSceneAutoUpdate = scene.autoUpdate;
-			var currentSceneBackground = scene.background;
-			var currentShadowMapEnabled = renderer.shadowMap.enabled;
-			scene.autoUpdate = false;
-			scene.background = null;
-			renderer.autoClear = false;
-			renderer.shadowMap.enabled = false;
-			scene.traverse( setOutlineMaterial );
-			renderer.render( scene, camera );
-			scene.traverse( restoreOriginalMaterial );
-			cleanupCache();
-			scene.autoUpdate = currentSceneAutoUpdate;
-			scene.background = currentSceneBackground;
-			renderer.autoClear = currentAutoClear;
-			renderer.shadowMap.enabled = currentShadowMapEnabled;
+			};
+			/*
+		 * See #9918
+		 *
+		 * The following property copies and wrapper methods enable
+		 * OutlineEffect to be called from other *Effect, like
+		 *
+		 * effect = new StereoEffect( new OutlineEffect( renderer ) );
+		 *
+		 * function render () {
+		 *
+			 * 	effect.render( scene, camera );
+		 *
+		 * }
+		 */
 
-		};
-		/*
-	 * See #9918
-	 *
-	 * The following property copies and wrapper methods enable
-	 * OutlineEffect to be called from other *Effect, like
-	 *
-	 * effect = new StereoEffect( new OutlineEffect( renderer ) );
-	 *
-	 * function render () {
-	 *
-		 * 	effect.render( scene, camera );
-	 *
-	 * }
-	 */
 
+			this.autoClear = renderer.autoClear;
+			this.domElement = renderer.domElement;
+			this.shadowMap = renderer.shadowMap;
 
-		this.autoClear = renderer.autoClear;
-		this.domElement = renderer.domElement;
-		this.shadowMap = renderer.shadowMap;
+			this.clear = function ( color, depth, stencil ) {
 
-		this.clear = function ( color, depth, stencil ) {
+				renderer.clear( color, depth, stencil );
 
-			renderer.clear( color, depth, stencil );
+			};
 
-		};
+			this.getPixelRatio = function () {
 
-		this.getPixelRatio = function () {
+				return renderer.getPixelRatio();
 
-			return renderer.getPixelRatio();
+			};
 
-		};
+			this.setPixelRatio = function ( value ) {
 
-		this.setPixelRatio = function ( value ) {
+				renderer.setPixelRatio( value );
 
-			renderer.setPixelRatio( value );
+			};
 
-		};
+			this.getSize = function ( target ) {
 
-		this.getSize = function ( target ) {
+				return renderer.getSize( target );
 
-			return renderer.getSize( target );
+			};
 
-		};
+			this.setSize = function ( width, height, updateStyle ) {
 
-		this.setSize = function ( width, height, updateStyle ) {
+				renderer.setSize( width, height, updateStyle );
 
-			renderer.setSize( width, height, updateStyle );
+			};
 
-		};
+			this.setViewport = function ( x, y, width, height ) {
 
-		this.setViewport = function ( x, y, width, height ) {
+				renderer.setViewport( x, y, width, height );
 
-			renderer.setViewport( x, y, width, height );
+			};
 
-		};
+			this.setScissor = function ( x, y, width, height ) {
 
-		this.setScissor = function ( x, y, width, height ) {
+				renderer.setScissor( x, y, width, height );
 
-			renderer.setScissor( x, y, width, height );
+			};
 
-		};
+			this.setScissorTest = function ( boolean ) {
 
-		this.setScissorTest = function ( boolean ) {
+				renderer.setScissorTest( boolean );
 
-			renderer.setScissorTest( boolean );
+			};
 
-		};
+			this.setRenderTarget = function ( renderTarget ) {
 
-		this.setRenderTarget = function ( renderTarget ) {
+				renderer.setRenderTarget( renderTarget );
 
-			renderer.setRenderTarget( renderTarget );
+			};
 
-		};
+		}
 
-	};
+	}
 
 	THREE.OutlineEffect = OutlineEffect;
 

+ 48 - 44
examples/js/effects/ParallaxBarrierEffect.js

@@ -1,70 +1,74 @@
 ( function () {
 
-	var ParallaxBarrierEffect = function ( renderer ) {
+	class ParallaxBarrierEffect {
 
-		var _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+		constructor( renderer ) {
 
-		var _scene = new THREE.Scene();
+			const _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
 
-		var _stereo = new THREE.StereoCamera();
+			const _scene = new THREE.Scene();
 
-		var _params = {
-			minFilter: THREE.LinearFilter,
-			magFilter: THREE.NearestFilter,
-			format: THREE.RGBAFormat
-		};
+			const _stereo = new THREE.StereoCamera();
 
-		var _renderTargetL = new THREE.WebGLRenderTarget( 512, 512, _params );
+			const _params = {
+				minFilter: THREE.LinearFilter,
+				magFilter: THREE.NearestFilter,
+				format: THREE.RGBAFormat
+			};
 
-		var _renderTargetR = new THREE.WebGLRenderTarget( 512, 512, _params );
+			const _renderTargetL = new THREE.WebGLRenderTarget( 512, 512, _params );
 
-		var _material = new THREE.ShaderMaterial( {
-			uniforms: {
-				'mapLeft': {
-					value: _renderTargetL.texture
+			const _renderTargetR = new THREE.WebGLRenderTarget( 512, 512, _params );
+
+			const _material = new THREE.ShaderMaterial( {
+				uniforms: {
+					'mapLeft': {
+						value: _renderTargetL.texture
+					},
+					'mapRight': {
+						value: _renderTargetR.texture
+					}
 				},
-				'mapRight': {
-					value: _renderTargetR.texture
-				}
-			},
-			vertexShader: [ 'varying vec2 vUv;', 'void main() {', '	vUv = vec2( uv.x, uv.y );', '	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', '}' ].join( '\n' ),
-			fragmentShader: [ 'uniform sampler2D mapLeft;', 'uniform sampler2D mapRight;', 'varying vec2 vUv;', 'void main() {', '	vec2 uv = vUv;', '	if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {', '		gl_FragColor = texture2D( mapLeft, uv );', '	} else {', '		gl_FragColor = texture2D( mapRight, uv );', '	}', '}' ].join( '\n' )
-		} );
+				vertexShader: [ 'varying vec2 vUv;', 'void main() {', '	vUv = vec2( uv.x, uv.y );', '	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', '}' ].join( '\n' ),
+				fragmentShader: [ 'uniform sampler2D mapLeft;', 'uniform sampler2D mapRight;', 'varying vec2 vUv;', 'void main() {', '	vec2 uv = vUv;', '	if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {', '		gl_FragColor = texture2D( mapLeft, uv );', '	} else {', '		gl_FragColor = texture2D( mapRight, uv );', '	}', '}' ].join( '\n' )
+			} );
+
+			const mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), _material );
 
-		var mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), _material );
+			_scene.add( mesh );
 
-		_scene.add( mesh );
+			this.setSize = function ( width, height ) {
 
-		this.setSize = function ( width, height ) {
+				renderer.setSize( width, height );
+				const pixelRatio = renderer.getPixelRatio();
 
-			renderer.setSize( width, height );
-			var pixelRatio = renderer.getPixelRatio();
+				_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
 
-			_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
+				_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
 
-			_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
+			};
 
-		};
+			this.render = function ( scene, camera ) {
 
-		this.render = function ( scene, camera ) {
+				scene.updateMatrixWorld();
+				if ( camera.parent === null ) camera.updateMatrixWorld();
 
-			scene.updateMatrixWorld();
-			if ( camera.parent === null ) camera.updateMatrixWorld();
+				_stereo.update( camera );
 
-			_stereo.update( camera );
+				renderer.setRenderTarget( _renderTargetL );
+				renderer.clear();
+				renderer.render( scene, _stereo.cameraL );
+				renderer.setRenderTarget( _renderTargetR );
+				renderer.clear();
+				renderer.render( scene, _stereo.cameraR );
+				renderer.setRenderTarget( null );
+				renderer.render( _scene, _camera );
 
-			renderer.setRenderTarget( _renderTargetL );
-			renderer.clear();
-			renderer.render( scene, _stereo.cameraL );
-			renderer.setRenderTarget( _renderTargetR );
-			renderer.clear();
-			renderer.render( scene, _stereo.cameraR );
-			renderer.setRenderTarget( null );
-			renderer.render( _scene, _camera );
+			};
 
-		};
+		}
 
-	};
+	}
 
 	THREE.ParallaxBarrierEffect = ParallaxBarrierEffect;
 

+ 84 - 80
examples/js/effects/PeppersGhostEffect.js

@@ -4,158 +4,162 @@
  * peppers ghost effect based on http://www.instructables.com/id/Reflective-Prism/?ALLSTEPS
  */
 
-	var PeppersGhostEffect = function ( renderer ) {
+	class PeppersGhostEffect {
 
-		var scope = this;
-		scope.cameraDistance = 15;
-		scope.reflectFromAbove = false; // Internals
+		constructor( renderer ) {
 
-		var _halfWidth, _width, _height;
+			const scope = this;
+			scope.cameraDistance = 15;
+			scope.reflectFromAbove = false; // Internals
 
-		var _cameraF = new THREE.PerspectiveCamera(); //front
+			let _halfWidth, _width, _height;
 
+			const _cameraF = new THREE.PerspectiveCamera(); //front
 
-		var _cameraB = new THREE.PerspectiveCamera(); //back
 
+			const _cameraB = new THREE.PerspectiveCamera(); //back
 
-		var _cameraL = new THREE.PerspectiveCamera(); //left
 
+			const _cameraL = new THREE.PerspectiveCamera(); //left
 
-		var _cameraR = new THREE.PerspectiveCamera(); //right
 
+			const _cameraR = new THREE.PerspectiveCamera(); //right
 
-		var _position = new THREE.Vector3();
 
-		var _quaternion = new THREE.Quaternion();
+			const _position = new THREE.Vector3();
 
-		var _scale = new THREE.Vector3(); // Initialization
+			const _quaternion = new THREE.Quaternion();
 
+			const _scale = new THREE.Vector3(); // Initialization
 
-		renderer.autoClear = false;
 
-		this.setSize = function ( width, height ) {
+			renderer.autoClear = false;
 
-			_halfWidth = width / 2;
+			this.setSize = function ( width, height ) {
 
-			if ( width < height ) {
+				_halfWidth = width / 2;
 
-				_width = width / 3;
-				_height = width / 3;
+				if ( width < height ) {
 
-			} else {
+					_width = width / 3;
+					_height = width / 3;
 
-				_width = height / 3;
-				_height = height / 3;
+				} else {
 
-			}
+					_width = height / 3;
+					_height = height / 3;
 
-			renderer.setSize( width, height );
+				}
 
-		};
+				renderer.setSize( width, height );
 
-		this.render = function ( scene, camera ) {
+			};
 
-			scene.updateMatrixWorld();
-			if ( camera.parent === null ) camera.updateMatrixWorld();
-			camera.matrixWorld.decompose( _position, _quaternion, _scale ); // front
+			this.render = function ( scene, camera ) {
 
-			_cameraF.position.copy( _position );
+				scene.updateMatrixWorld();
+				if ( camera.parent === null ) camera.updateMatrixWorld();
+				camera.matrixWorld.decompose( _position, _quaternion, _scale ); // front
 
-			_cameraF.quaternion.copy( _quaternion );
+				_cameraF.position.copy( _position );
 
-			_cameraF.translateZ( scope.cameraDistance );
+				_cameraF.quaternion.copy( _quaternion );
 
-			_cameraF.lookAt( scene.position ); // back
+				_cameraF.translateZ( scope.cameraDistance );
 
+				_cameraF.lookAt( scene.position ); // back
 
-			_cameraB.position.copy( _position );
 
-			_cameraB.quaternion.copy( _quaternion );
+				_cameraB.position.copy( _position );
 
-			_cameraB.translateZ( - scope.cameraDistance );
+				_cameraB.quaternion.copy( _quaternion );
 
-			_cameraB.lookAt( scene.position );
+				_cameraB.translateZ( - scope.cameraDistance );
 
-			_cameraB.rotation.z += 180 * ( Math.PI / 180 ); // left
+				_cameraB.lookAt( scene.position );
 
-			_cameraL.position.copy( _position );
+				_cameraB.rotation.z += 180 * ( Math.PI / 180 ); // left
 
-			_cameraL.quaternion.copy( _quaternion );
+				_cameraL.position.copy( _position );
 
-			_cameraL.translateX( - scope.cameraDistance );
+				_cameraL.quaternion.copy( _quaternion );
 
-			_cameraL.lookAt( scene.position );
+				_cameraL.translateX( - scope.cameraDistance );
 
-			_cameraL.rotation.x += 90 * ( Math.PI / 180 ); // right
+				_cameraL.lookAt( scene.position );
 
-			_cameraR.position.copy( _position );
+				_cameraL.rotation.x += 90 * ( Math.PI / 180 ); // right
 
-			_cameraR.quaternion.copy( _quaternion );
+				_cameraR.position.copy( _position );
 
-			_cameraR.translateX( scope.cameraDistance );
+				_cameraR.quaternion.copy( _quaternion );
 
-			_cameraR.lookAt( scene.position );
+				_cameraR.translateX( scope.cameraDistance );
 
-			_cameraR.rotation.x += 90 * ( Math.PI / 180 );
-			renderer.clear();
-			renderer.setScissorTest( true );
-			renderer.setScissor( _halfWidth - _width / 2, _height * 2, _width, _height );
-			renderer.setViewport( _halfWidth - _width / 2, _height * 2, _width, _height );
+				_cameraR.lookAt( scene.position );
 
-			if ( scope.reflectFromAbove ) {
+				_cameraR.rotation.x += 90 * ( Math.PI / 180 );
+				renderer.clear();
+				renderer.setScissorTest( true );
+				renderer.setScissor( _halfWidth - _width / 2, _height * 2, _width, _height );
+				renderer.setViewport( _halfWidth - _width / 2, _height * 2, _width, _height );
 
-				renderer.render( scene, _cameraB );
+				if ( scope.reflectFromAbove ) {
 
-			} else {
+					renderer.render( scene, _cameraB );
 
-				renderer.render( scene, _cameraF );
+				} else {
 
-			}
+					renderer.render( scene, _cameraF );
 
-			renderer.setScissor( _halfWidth - _width / 2, 0, _width, _height );
-			renderer.setViewport( _halfWidth - _width / 2, 0, _width, _height );
+				}
 
-			if ( scope.reflectFromAbove ) {
+				renderer.setScissor( _halfWidth - _width / 2, 0, _width, _height );
+				renderer.setViewport( _halfWidth - _width / 2, 0, _width, _height );
 
-				renderer.render( scene, _cameraF );
+				if ( scope.reflectFromAbove ) {
 
-			} else {
+					renderer.render( scene, _cameraF );
 
-				renderer.render( scene, _cameraB );
+				} else {
 
-			}
+					renderer.render( scene, _cameraB );
 
-			renderer.setScissor( _halfWidth - _width / 2 - _width, _height, _width, _height );
-			renderer.setViewport( _halfWidth - _width / 2 - _width, _height, _width, _height );
+				}
 
-			if ( scope.reflectFromAbove ) {
+				renderer.setScissor( _halfWidth - _width / 2 - _width, _height, _width, _height );
+				renderer.setViewport( _halfWidth - _width / 2 - _width, _height, _width, _height );
 
-				renderer.render( scene, _cameraR );
+				if ( scope.reflectFromAbove ) {
 
-			} else {
+					renderer.render( scene, _cameraR );
 
-				renderer.render( scene, _cameraL );
+				} else {
 
-			}
+					renderer.render( scene, _cameraL );
 
-			renderer.setScissor( _halfWidth + _width / 2, _height, _width, _height );
-			renderer.setViewport( _halfWidth + _width / 2, _height, _width, _height );
+				}
 
-			if ( scope.reflectFromAbove ) {
+				renderer.setScissor( _halfWidth + _width / 2, _height, _width, _height );
+				renderer.setViewport( _halfWidth + _width / 2, _height, _width, _height );
 
-				renderer.render( scene, _cameraL );
+				if ( scope.reflectFromAbove ) {
 
-			} else {
+					renderer.render( scene, _cameraL );
 
-				renderer.render( scene, _cameraR );
+				} else {
 
-			}
+					renderer.render( scene, _cameraR );
 
-			renderer.setScissorTest( false );
+				}
 
-		};
+				renderer.setScissorTest( false );
 
-	};
+			};
+
+		}
+
+	}
 
 	THREE.PeppersGhostEffect = PeppersGhostEffect;
 

+ 30 - 26
examples/js/effects/StereoEffect.js

@@ -1,45 +1,49 @@
 ( function () {
 
-	var StereoEffect = function ( renderer ) {
+	class StereoEffect {
 
-		var _stereo = new THREE.StereoCamera();
+		constructor( renderer ) {
 
-		_stereo.aspect = 0.5;
-		var size = new THREE.Vector2();
+			const _stereo = new THREE.StereoCamera();
 
-		this.setEyeSeparation = function ( eyeSep ) {
+			_stereo.aspect = 0.5;
+			const size = new THREE.Vector2();
 
-			_stereo.eyeSep = eyeSep;
+			this.setEyeSeparation = function ( eyeSep ) {
 
-		};
+				_stereo.eyeSep = eyeSep;
 
-		this.setSize = function ( width, height ) {
+			};
 
-			renderer.setSize( width, height );
+			this.setSize = function ( width, height ) {
 
-		};
+				renderer.setSize( width, height );
 
-		this.render = function ( scene, camera ) {
+			};
 
-			scene.updateMatrixWorld();
-			if ( camera.parent === null ) camera.updateMatrixWorld();
+			this.render = function ( scene, camera ) {
 
-			_stereo.update( camera );
+				scene.updateMatrixWorld();
+				if ( camera.parent === null ) camera.updateMatrixWorld();
 
-			renderer.getSize( size );
-			if ( renderer.autoClear ) renderer.clear();
-			renderer.setScissorTest( true );
-			renderer.setScissor( 0, 0, size.width / 2, size.height );
-			renderer.setViewport( 0, 0, size.width / 2, size.height );
-			renderer.render( scene, _stereo.cameraL );
-			renderer.setScissor( size.width / 2, 0, size.width / 2, size.height );
-			renderer.setViewport( size.width / 2, 0, size.width / 2, size.height );
-			renderer.render( scene, _stereo.cameraR );
-			renderer.setScissorTest( false );
+				_stereo.update( camera );
 
-		};
+				renderer.getSize( size );
+				if ( renderer.autoClear ) renderer.clear();
+				renderer.setScissorTest( true );
+				renderer.setScissor( 0, 0, size.width / 2, size.height );
+				renderer.setViewport( 0, 0, size.width / 2, size.height );
+				renderer.render( scene, _stereo.cameraL );
+				renderer.setScissor( size.width / 2, 0, size.width / 2, size.height );
+				renderer.setViewport( size.width / 2, 0, size.width / 2, size.height );
+				renderer.render( scene, _stereo.cameraR );
+				renderer.setScissorTest( false );
 
-	};
+			};
+
+		}
+
+	}
 
 	THREE.StereoEffect = StereoEffect;
 

+ 3105 - 2282
examples/js/loaders/GLTFLoader.js

@@ -1,426 +1,563 @@
 ( function () {
-var GLTFLoader = function () {
-	function GLTFLoader(manager) {
-		THREE.Loader.call(this, manager);
-		this.dracoLoader = null;
-		this.ktx2Loader = null;
-		this.meshoptDecoder = null;
-		this.pluginCallbacks = [];
-		this.register(function (parser) {
-			return new GLTFMaterialsClearcoatExtension(parser);
-		});
-		this.register(function (parser) {
-			return new GLTFTextureBasisUExtension(parser);
-		});
-		this.register(function (parser) {
-			return new GLTFTextureWebPExtension(parser);
-		});
-		this.register(function (parser) {
-			return new GLTFMaterialsTransmissionExtension(parser);
-		});
-		this.register(function (parser) {
-			return new GLTFLightsExtension(parser);
-		});
-		this.register(function (parser) {
-			return new GLTFMeshoptCompression(parser);
-		});
-	}
-
-	GLTFLoader.prototype = Object.assign(Object.create(THREE.Loader.prototype), {
-		constructor: GLTFLoader,
-		load: function (url, onLoad, onProgress, onError) {
-			var scope = this;
-			var resourcePath;
-
-			if (this.resourcePath !== '') {
-				resourcePath = this.resourcePath;
-			} else if (this.path !== '') {
-				resourcePath = this.path;
-			} else {
-				resourcePath = THREE.LoaderUtils.extractUrlBase(url);
-			} // Tells the LoadingManager to track an extra item, which resolves after
-			// the model is fully loaded. This means the count of items loaded will
-			// be incorrect, but ensures manager.onLoad() does not fire early.
 
+	var GLTFLoader = function () {
+
+		function GLTFLoader( manager ) {
+
+			THREE.Loader.call( this, manager );
+			this.dracoLoader = null;
+			this.ktx2Loader = null;
+			this.meshoptDecoder = null;
+			this.pluginCallbacks = [];
+			this.register( function ( parser ) {
+
+				return new GLTFMaterialsClearcoatExtension( parser );
+
+			} );
+			this.register( function ( parser ) {
+
+				return new GLTFTextureBasisUExtension( parser );
+
+			} );
+			this.register( function ( parser ) {
+
+				return new GLTFTextureWebPExtension( parser );
+
+			} );
+			this.register( function ( parser ) {
+
+				return new GLTFMaterialsTransmissionExtension( parser );
+
+			} );
+			this.register( function ( parser ) {
+
+				return new GLTFLightsExtension( parser );
+
+			} );
+			this.register( function ( parser ) {
+
+				return new GLTFMeshoptCompression( parser );
+
+			} );
+
+		}
+
+		GLTFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+			constructor: GLTFLoader,
+			load: function ( url, onLoad, onProgress, onError ) {
+
+				var scope = this;
+				var resourcePath;
+
+				if ( this.resourcePath !== '' ) {
 
-			this.manager.itemStart(url);
+					resourcePath = this.resourcePath;
+
+				} else if ( this.path !== '' ) {
+
+					resourcePath = this.path;
 
-			var _onError = function (e) {
-				if (onError) {
-					onError(e);
 				} else {
-					console.error(e);
-				}
 
-				scope.manager.itemError(url);
-				scope.manager.itemEnd(url);
-			};
+					resourcePath = THREE.LoaderUtils.extractUrlBase( url );
 
-			var loader = new THREE.FileLoader(this.manager);
-			loader.setPath(this.path);
-			loader.setResponseType('arraybuffer');
-			loader.setRequestHeader(this.requestHeader);
-			loader.setWithCredentials(this.withCredentials);
-			loader.load(url, function (data) {
-				try {
-					scope.parse(data, resourcePath, function (gltf) {
-						onLoad(gltf);
-						scope.manager.itemEnd(url);
-					}, _onError);
-				} catch (e) {
-					_onError(e);
-				}
-			}, onProgress, _onError);
-		},
-		setDRACOLoader: function (dracoLoader) {
-			this.dracoLoader = dracoLoader;
-			return this;
-		},
-		setDDSLoader: function () {
-			throw new Error('THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".');
-		},
-		setKTX2Loader: function (ktx2Loader) {
-			this.ktx2Loader = ktx2Loader;
-			return this;
-		},
-		setMeshoptDecoder: function (meshoptDecoder) {
-			this.meshoptDecoder = meshoptDecoder;
-			return this;
-		},
-		register: function (callback) {
-			if (this.pluginCallbacks.indexOf(callback) === -1) {
-				this.pluginCallbacks.push(callback);
-			}
+				} // Tells the LoadingManager to track an extra item, which resolves after
+				// the model is fully loaded. This means the count of items loaded will
+				// be incorrect, but ensures manager.onLoad() does not fire early.
 
-			return this;
-		},
-		unregister: function (callback) {
-			if (this.pluginCallbacks.indexOf(callback) !== -1) {
-				this.pluginCallbacks.splice(this.pluginCallbacks.indexOf(callback), 1);
-			}
 
-			return this;
-		},
-		parse: function (data, path, onLoad, onError) {
-			var content;
-			var extensions = {};
-			var plugins = {};
-
-			if (typeof data === 'string') {
-				content = data;
-			} else {
-				var magic = THREE.LoaderUtils.decodeText(new Uint8Array(data, 0, 4));
+				this.manager.itemStart( url );
+
+				var _onError = function ( e ) {
+
+					if ( onError ) {
+
+						onError( e );
+
+					} else {
+
+						console.error( e );
+
+					}
+
+					scope.manager.itemError( url );
+					scope.manager.itemEnd( url );
+
+				};
+
+				var loader = new THREE.FileLoader( this.manager );
+				loader.setPath( this.path );
+				loader.setResponseType( 'arraybuffer' );
+				loader.setRequestHeader( this.requestHeader );
+				loader.setWithCredentials( this.withCredentials );
+				loader.load( url, function ( data ) {
 
-				if (magic === BINARY_EXTENSION_HEADER_MAGIC) {
 					try {
-						extensions[EXTENSIONS.KHR_BINARY_GLTF] = new GLTFBinaryExtension(data);
-					} catch (error) {
-						if (onError) onError(error);
-						return;
+
+						scope.parse( data, resourcePath, function ( gltf ) {
+
+							onLoad( gltf );
+							scope.manager.itemEnd( url );
+
+						}, _onError );
+
+					} catch ( e ) {
+
+						_onError( e );
+
 					}
 
-					content = extensions[EXTENSIONS.KHR_BINARY_GLTF].content;
+				}, onProgress, _onError );
+
+			},
+			setDRACOLoader: function ( dracoLoader ) {
+
+				this.dracoLoader = dracoLoader;
+				return this;
+
+			},
+			setDDSLoader: function () {
+
+				throw new Error( 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' );
+
+			},
+			setKTX2Loader: function ( ktx2Loader ) {
+
+				this.ktx2Loader = ktx2Loader;
+				return this;
+
+			},
+			setMeshoptDecoder: function ( meshoptDecoder ) {
+
+				this.meshoptDecoder = meshoptDecoder;
+				return this;
+
+			},
+			register: function ( callback ) {
+
+				if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) {
+
+					this.pluginCallbacks.push( callback );
+
+				}
+
+				return this;
+
+			},
+			unregister: function ( callback ) {
+
+				if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) {
+
+					this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 );
+
+				}
+
+				return this;
+
+			},
+			parse: function ( data, path, onLoad, onError ) {
+
+				var content;
+				var extensions = {};
+				var plugins = {};
+
+				if ( typeof data === 'string' ) {
+
+					content = data;
+
 				} else {
-					content = THREE.LoaderUtils.decodeText(new Uint8Array(data));
+
+					var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) );
+
+					if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {
+
+						try {
+
+							extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );
+
+						} catch ( error ) {
+
+							if ( onError ) onError( error );
+							return;
+
+						}
+
+						content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;
+
+					} else {
+
+						content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
+
+					}
+
 				}
-			}
 
-			var json = JSON.parse(content);
+				var json = JSON.parse( content );
 
-			if (json.asset === undefined || json.asset.version[0] < 2) {
-				if (onError) onError(new Error('THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.'));
-				return;
-			}
+				if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
 
-			var parser = new GLTFParser(json, {
-				path: path || this.resourcePath || '',
-				crossOrigin: this.crossOrigin,
-				requestHeader: this.requestHeader,
-				manager: this.manager,
-				ktx2Loader: this.ktx2Loader,
-				meshoptDecoder: this.meshoptDecoder
-			});
-			parser.fileLoader.setRequestHeader(this.requestHeader);
+					if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) );
+					return;
 
-			for (var i = 0; i < this.pluginCallbacks.length; i++) {
-				var plugin = this.pluginCallbacks[i](parser);
-				plugins[plugin.name] = plugin; // Workaround to avoid determining as unknown extension
-				// in addUnknownExtensionsToUserData().
-				// Remove this workaround if we move all the existing
-				// extension handlers to plugin system
+				}
 
-				extensions[plugin.name] = true;
-			}
+				var parser = new GLTFParser( json, {
+					path: path || this.resourcePath || '',
+					crossOrigin: this.crossOrigin,
+					requestHeader: this.requestHeader,
+					manager: this.manager,
+					ktx2Loader: this.ktx2Loader,
+					meshoptDecoder: this.meshoptDecoder
+				} );
+				parser.fileLoader.setRequestHeader( this.requestHeader );
 
-			if (json.extensionsUsed) {
-				for (var i = 0; i < json.extensionsUsed.length; ++i) {
-					var extensionName = json.extensionsUsed[i];
-					var extensionsRequired = json.extensionsRequired || [];
+				for ( var i = 0; i < this.pluginCallbacks.length; i ++ ) {
 
-					switch (extensionName) {
-						case EXTENSIONS.KHR_MATERIALS_UNLIT:
-							extensions[extensionName] = new GLTFMaterialsUnlitExtension();
-							break;
+					var plugin = this.pluginCallbacks[ i ]( parser );
+					plugins[ plugin.name ] = plugin; // Workaround to avoid determining as unknown extension
+					// in addUnknownExtensionsToUserData().
+					// Remove this workaround if we move all the existing
+					// extension handlers to plugin system
 
-						case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
-							extensions[extensionName] = new GLTFMaterialsPbrSpecularGlossinessExtension();
-							break;
+					extensions[ plugin.name ] = true;
 
-						case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
-							extensions[extensionName] = new GLTFDracoMeshCompressionExtension(json, this.dracoLoader);
-							break;
+				}
 
-						case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
-							extensions[extensionName] = new GLTFTextureTransformExtension();
-							break;
+				if ( json.extensionsUsed ) {
 
-						case EXTENSIONS.KHR_MESH_QUANTIZATION:
-							extensions[extensionName] = new GLTFMeshQuantizationExtension();
-							break;
+					for ( var i = 0; i < json.extensionsUsed.length; ++ i ) {
 
-						default:
-							if (extensionsRequired.indexOf(extensionName) >= 0 && plugins[extensionName] === undefined) {
-								console.warn('THREE.GLTFLoader: Unknown extension "' + extensionName + '".');
-							}
+						var extensionName = json.extensionsUsed[ i ];
+						var extensionsRequired = json.extensionsRequired || [];
+
+						switch ( extensionName ) {
+
+							case EXTENSIONS.KHR_MATERIALS_UNLIT:
+								extensions[ extensionName ] = new GLTFMaterialsUnlitExtension();
+								break;
+
+							case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
+								extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension();
+								break;
+
+							case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
+								extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
+								break;
+
+							case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
+								extensions[ extensionName ] = new GLTFTextureTransformExtension();
+								break;
+
+							case EXTENSIONS.KHR_MESH_QUANTIZATION:
+								extensions[ extensionName ] = new GLTFMeshQuantizationExtension();
+								break;
+
+							default:
+								if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) {
+
+									console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' );
+
+								}
+
+						}
 
 					}
+
 				}
+
+				parser.setExtensions( extensions );
+				parser.setPlugins( plugins );
+				parser.parse( onLoad, onError );
+
 			}
+		} );
+		/* GLTFREGISTRY */
+
+		function GLTFRegistry() {
+
+			var objects = {};
+			return {
+				get: function ( key ) {
+
+					return objects[ key ];
+
+				},
+				add: function ( key, object ) {
+
+					objects[ key ] = object;
+
+				},
+				remove: function ( key ) {
+
+					delete objects[ key ];
+
+				},
+				removeAll: function () {
+
+					objects = {};
+
+				}
+			};
 
-			parser.setExtensions(extensions);
-			parser.setPlugins(plugins);
-			parser.parse(onLoad, onError);
 		}
-	});
-	/* GLTFREGISTRY */
-
-	function GLTFRegistry() {
-		var objects = {};
-		return {
-			get: function (key) {
-				return objects[key];
-			},
-			add: function (key, object) {
-				objects[key] = object;
-			},
-			remove: function (key) {
-				delete objects[key];
-			},
-			removeAll: function () {
-				objects = {};
-			}
+		/*********************************/
+
+		/********** EXTENSIONS ***********/
+
+		/*********************************/
+
+
+		var EXTENSIONS = {
+			KHR_BINARY_GLTF: 'KHR_binary_glTF',
+			KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
+			KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
+			KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat',
+			KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
+			KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission',
+			KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
+			KHR_TEXTURE_BASISU: 'KHR_texture_basisu',
+			KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
+			KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
+			EXT_TEXTURE_WEBP: 'EXT_texture_webp',
+			EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression'
 		};
-	}
-	/*********************************/
-
-	/********** EXTENSIONS ***********/
-
-	/*********************************/
-
-
-	var EXTENSIONS = {
-		KHR_BINARY_GLTF: 'KHR_binary_glTF',
-		KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
-		KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
-		KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat',
-		KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
-		KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission',
-		KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
-		KHR_TEXTURE_BASISU: 'KHR_texture_basisu',
-		KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
-		KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
-		EXT_TEXTURE_WEBP: 'EXT_texture_webp',
-		EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression'
-	};
-	/**
+		/**
 	 * Punctual Lights Extension
 	 *
 	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
 	 */
 
-	function GLTFLightsExtension(parser) {
-		this.parser = parser;
-		this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; // THREE.Object3D instance caches
+		function GLTFLightsExtension( parser ) {
 
-		this.cache = {
-			refs: {},
-			uses: {}
-		};
-	}
+			this.parser = parser;
+			this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; // THREE.Object3D instance caches
+
+			this.cache = {
+				refs: {},
+				uses: {}
+			};
+
+		}
+
+		GLTFLightsExtension.prototype._markDefs = function () {
+
+			var parser = this.parser;
+			var nodeDefs = this.parser.json.nodes || [];
+
+			for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
+
+				var nodeDef = nodeDefs[ nodeIndex ];
+
+				if ( nodeDef.extensions && nodeDef.extensions[ this.name ] && nodeDef.extensions[ this.name ].light !== undefined ) {
 
-	GLTFLightsExtension.prototype._markDefs = function () {
-		var parser = this.parser;
-		var nodeDefs = this.parser.json.nodes || [];
+					parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light );
 
-		for (var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex++) {
-			var nodeDef = nodeDefs[nodeIndex];
+				}
 
-			if (nodeDef.extensions && nodeDef.extensions[this.name] && nodeDef.extensions[this.name].light !== undefined) {
-				parser._addNodeRef(this.cache, nodeDef.extensions[this.name].light);
 			}
-		}
-	};
-
-	GLTFLightsExtension.prototype._loadLight = function (lightIndex) {
-		var parser = this.parser;
-		var cacheKey = 'light:' + lightIndex;
-		var dependency = parser.cache.get(cacheKey);
-		if (dependency) return dependency;
-		var json = parser.json;
-		var extensions = json.extensions && json.extensions[this.name] || {};
-		var lightDefs = extensions.lights || [];
-		var lightDef = lightDefs[lightIndex];
-		var lightNode;
-		var color = new THREE.Color(0xffffff);
-		if (lightDef.color !== undefined) color.fromArray(lightDef.color);
-		var range = lightDef.range !== undefined ? lightDef.range : 0;
-
-		switch (lightDef.type) {
-			case 'directional':
-				lightNode = new THREE.DirectionalLight(color);
-				lightNode.target.position.set(0, 0, -1);
-				lightNode.add(lightNode.target);
-				break;
-
-			case 'point':
-				lightNode = new THREE.PointLight(color);
-				lightNode.distance = range;
-				break;
-
-			case 'spot':
-				lightNode = new THREE.SpotLight(color);
-				lightNode.distance = range; // Handle spotlight properties.
-
-				lightDef.spot = lightDef.spot || {};
-				lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
-				lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
-				lightNode.angle = lightDef.spot.outerConeAngle;
-				lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
-				lightNode.target.position.set(0, 0, -1);
-				lightNode.add(lightNode.target);
-				break;
-
-			default:
-				throw new Error('THREE.GLTFLoader: Unexpected light type: ' + lightDef.type);
-		} // Some lights (e.g. spot) default to a position other than the origin. Reset the position
-		// here, because node-level parsing will only override position if explicitly specified.
-
-
-		lightNode.position.set(0, 0, 0);
-		lightNode.decay = 2;
-		if (lightDef.intensity !== undefined) lightNode.intensity = lightDef.intensity;
-		lightNode.name = parser.createUniqueName(lightDef.name || 'light_' + lightIndex);
-		dependency = Promise.resolve(lightNode);
-		parser.cache.add(cacheKey, dependency);
-		return dependency;
-	};
-
-	GLTFLightsExtension.prototype.createNodeAttachment = function (nodeIndex) {
-		var self = this;
-		var parser = this.parser;
-		var json = parser.json;
-		var nodeDef = json.nodes[nodeIndex];
-		var lightDef = nodeDef.extensions && nodeDef.extensions[this.name] || {};
-		var lightIndex = lightDef.light;
-		if (lightIndex === undefined) return null;
-		return this._loadLight(lightIndex).then(function (light) {
-			return parser._getNodeRef(self.cache, lightIndex, light);
-		});
-	};
-	/**
+
+		};
+
+		GLTFLightsExtension.prototype._loadLight = function ( lightIndex ) {
+
+			var parser = this.parser;
+			var cacheKey = 'light:' + lightIndex;
+			var dependency = parser.cache.get( cacheKey );
+			if ( dependency ) return dependency;
+			var json = parser.json;
+			var extensions = json.extensions && json.extensions[ this.name ] || {};
+			var lightDefs = extensions.lights || [];
+			var lightDef = lightDefs[ lightIndex ];
+			var lightNode;
+			var color = new THREE.Color( 0xffffff );
+			if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );
+			var range = lightDef.range !== undefined ? lightDef.range : 0;
+
+			switch ( lightDef.type ) {
+
+				case 'directional':
+					lightNode = new THREE.DirectionalLight( color );
+					lightNode.target.position.set( 0, 0, - 1 );
+					lightNode.add( lightNode.target );
+					break;
+
+				case 'point':
+					lightNode = new THREE.PointLight( color );
+					lightNode.distance = range;
+					break;
+
+				case 'spot':
+					lightNode = new THREE.SpotLight( color );
+					lightNode.distance = range; // Handle spotlight properties.
+
+					lightDef.spot = lightDef.spot || {};
+					lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
+					lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
+					lightNode.angle = lightDef.spot.outerConeAngle;
+					lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
+					lightNode.target.position.set( 0, 0, - 1 );
+					lightNode.add( lightNode.target );
+					break;
+
+				default:
+					throw new Error( 'THREE.GLTFLoader: Unexpected light type: ' + lightDef.type );
+
+			} // Some lights (e.g. spot) default to a position other than the origin. Reset the position
+			// here, because node-level parsing will only override position if explicitly specified.
+
+
+			lightNode.position.set( 0, 0, 0 );
+			lightNode.decay = 2;
+			if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;
+			lightNode.name = parser.createUniqueName( lightDef.name || 'light_' + lightIndex );
+			dependency = Promise.resolve( lightNode );
+			parser.cache.add( cacheKey, dependency );
+			return dependency;
+
+		};
+
+		GLTFLightsExtension.prototype.createNodeAttachment = function ( nodeIndex ) {
+
+			var self = this;
+			var parser = this.parser;
+			var json = parser.json;
+			var nodeDef = json.nodes[ nodeIndex ];
+			var lightDef = nodeDef.extensions && nodeDef.extensions[ this.name ] || {};
+			var lightIndex = lightDef.light;
+			if ( lightIndex === undefined ) return null;
+			return this._loadLight( lightIndex ).then( function ( light ) {
+
+				return parser._getNodeRef( self.cache, lightIndex, light );
+
+			} );
+
+		};
+		/**
 	 * Unlit Materials Extension
 	 *
 	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
 	 */
 
 
-	function GLTFMaterialsUnlitExtension() {
-		this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
-	}
+		function GLTFMaterialsUnlitExtension() {
+
+			this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;
 
-	GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () {
-		return THREE.MeshBasicMaterial;
-	};
+		}
 
-	GLTFMaterialsUnlitExtension.prototype.extendParams = function (materialParams, materialDef, parser) {
-		var pending = [];
-		materialParams.color = new THREE.Color(1.0, 1.0, 1.0);
-		materialParams.opacity = 1.0;
-		var metallicRoughness = materialDef.pbrMetallicRoughness;
+		GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () {
 
-		if (metallicRoughness) {
-			if (Array.isArray(metallicRoughness.baseColorFactor)) {
-				var array = metallicRoughness.baseColorFactor;
-				materialParams.color.fromArray(array);
-				materialParams.opacity = array[3];
-			}
+			return THREE.MeshBasicMaterial;
+
+		};
+
+		GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) {
+
+			var pending = [];
+			materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
+			materialParams.opacity = 1.0;
+			var metallicRoughness = materialDef.pbrMetallicRoughness;
+
+			if ( metallicRoughness ) {
+
+				if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
+
+					var array = metallicRoughness.baseColorFactor;
+					materialParams.color.fromArray( array );
+					materialParams.opacity = array[ 3 ];
+
+				}
+
+				if ( metallicRoughness.baseColorTexture !== undefined ) {
+
+					pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
+
+				}
 
-			if (metallicRoughness.baseColorTexture !== undefined) {
-				pending.push(parser.assignTexture(materialParams, 'map', metallicRoughness.baseColorTexture));
 			}
-		}
 
-		return Promise.all(pending);
-	};
-	/**
+			return Promise.all( pending );
+
+		};
+		/**
 	 * Clearcoat Materials Extension
 	 *
 	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat
 	 */
 
 
-	function GLTFMaterialsClearcoatExtension(parser) {
-		this.parser = parser;
-		this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT;
-	}
+		function GLTFMaterialsClearcoatExtension( parser ) {
 
-	GLTFMaterialsClearcoatExtension.prototype.getMaterialType = function (materialIndex) {
-		var parser = this.parser;
-		var materialDef = parser.json.materials[materialIndex];
-		if (!materialDef.extensions || !materialDef.extensions[this.name]) return null;
-		return THREE.MeshPhysicalMaterial;
-	};
+			this.parser = parser;
+			this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT;
 
-	GLTFMaterialsClearcoatExtension.prototype.extendMaterialParams = function (materialIndex, materialParams) {
-		var parser = this.parser;
-		var materialDef = parser.json.materials[materialIndex];
-
-		if (!materialDef.extensions || !materialDef.extensions[this.name]) {
-			return Promise.resolve();
 		}
 
-		var pending = [];
-		var extension = materialDef.extensions[this.name];
+		GLTFMaterialsClearcoatExtension.prototype.getMaterialType = function ( materialIndex ) {
 
-		if (extension.clearcoatFactor !== undefined) {
-			materialParams.clearcoat = extension.clearcoatFactor;
-		}
+			var parser = this.parser;
+			var materialDef = parser.json.materials[ materialIndex ];
+			if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
+			return THREE.MeshPhysicalMaterial;
 
-		if (extension.clearcoatTexture !== undefined) {
-			pending.push(parser.assignTexture(materialParams, 'clearcoatMap', extension.clearcoatTexture));
-		}
+		};
 
-		if (extension.clearcoatRoughnessFactor !== undefined) {
-			materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor;
-		}
+		GLTFMaterialsClearcoatExtension.prototype.extendMaterialParams = function ( materialIndex, materialParams ) {
 
-		if (extension.clearcoatRoughnessTexture !== undefined) {
-			pending.push(parser.assignTexture(materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture));
-		}
+			var parser = this.parser;
+			var materialDef = parser.json.materials[ materialIndex ];
 
-		if (extension.clearcoatNormalTexture !== undefined) {
-			pending.push(parser.assignTexture(materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture));
+			if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
 
-			if (extension.clearcoatNormalTexture.scale !== undefined) {
-				var scale = extension.clearcoatNormalTexture.scale; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
+				return Promise.resolve();
 
-				materialParams.clearcoatNormalScale = new THREE.Vector2(scale, -scale);
 			}
-		}
 
-		return Promise.all(pending);
-	};
-	/**
+			var pending = [];
+			var extension = materialDef.extensions[ this.name ];
+
+			if ( extension.clearcoatFactor !== undefined ) {
+
+				materialParams.clearcoat = extension.clearcoatFactor;
+
+			}
+
+			if ( extension.clearcoatTexture !== undefined ) {
+
+				pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) );
+
+			}
+
+			if ( extension.clearcoatRoughnessFactor !== undefined ) {
+
+				materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor;
+
+			}
+
+			if ( extension.clearcoatRoughnessTexture !== undefined ) {
+
+				pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) );
+
+			}
+
+			if ( extension.clearcoatNormalTexture !== undefined ) {
+
+				pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) );
+
+				if ( extension.clearcoatNormalTexture.scale !== undefined ) {
+
+					var scale = extension.clearcoatNormalTexture.scale; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
+
+					materialParams.clearcoatNormalScale = new THREE.Vector2( scale, - scale );
+
+				}
+
+			}
+
+			return Promise.all( pending );
+
+		};
+		/**
 	 * Transmission Materials Extension
 	 *
 	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission
@@ -428,2326 +565,3012 @@ var GLTFLoader = function () {
 	 */
 
 
-	function GLTFMaterialsTransmissionExtension(parser) {
-		this.parser = parser;
-		this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION;
-	}
+		function GLTFMaterialsTransmissionExtension( parser ) {
+
+			this.parser = parser;
+			this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION;
+
+		}
+
+		GLTFMaterialsTransmissionExtension.prototype.getMaterialType = function ( materialIndex ) {
+
+			var parser = this.parser;
+			var materialDef = parser.json.materials[ materialIndex ];
+			if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
+			return THREE.MeshPhysicalMaterial;
+
+		};
+
+		GLTFMaterialsTransmissionExtension.prototype.extendMaterialParams = function ( materialIndex, materialParams ) {
+
+			var parser = this.parser;
+			var materialDef = parser.json.materials[ materialIndex ];
+
+			if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
+
+				return Promise.resolve();
+
+			}
+
+			var pending = [];
+			var extension = materialDef.extensions[ this.name ];
+
+			if ( extension.transmissionFactor !== undefined ) {
+
+				materialParams.transmission = extension.transmissionFactor;
+
+			}
+
+			if ( extension.transmissionTexture !== undefined ) {
+
+				pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) );
+
+			}
+
+			return Promise.all( pending );
+
+		};
+		/**
+	 * BasisU Texture Extension
+	 *
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu
+	 */
+
+
+		function GLTFTextureBasisUExtension( parser ) {
+
+			this.parser = parser;
+			this.name = EXTENSIONS.KHR_TEXTURE_BASISU;
+
+		}
+
+		GLTFTextureBasisUExtension.prototype.loadTexture = function ( textureIndex ) {
+
+			var parser = this.parser;
+			var json = parser.json;
+			var textureDef = json.textures[ textureIndex ];
+
+			if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) {
+
+				return null;
+
+			}
+
+			var extension = textureDef.extensions[ this.name ];
+			var source = json.images[ extension.source ];
+			var loader = parser.options.ktx2Loader;
+
+			if ( ! loader ) {
+
+				if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) {
+
+					throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' );
+
+				} else {
+
+					// Assumes that the extension is optional and that a fallback texture is present
+					return null;
+
+				}
+
+			}
+
+			return parser.loadTextureImage( textureIndex, source, loader );
+
+		};
+		/**
+	 * WebP Texture Extension
+	 *
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp
+	 */
+
+
+		function GLTFTextureWebPExtension( parser ) {
+
+			this.parser = parser;
+			this.name = EXTENSIONS.EXT_TEXTURE_WEBP;
+			this.isSupported = null;
+
+		}
+
+		GLTFTextureWebPExtension.prototype.loadTexture = function ( textureIndex ) {
+
+			var name = this.name;
+			var parser = this.parser;
+			var json = parser.json;
+			var textureDef = json.textures[ textureIndex ];
+
+			if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) {
+
+				return null;
+
+			}
+
+			var extension = textureDef.extensions[ name ];
+			var source = json.images[ extension.source ];
+			var loader = parser.textureLoader;
+
+			if ( source.uri ) {
+
+				var handler = parser.options.manager.getHandler( source.uri );
+				if ( handler !== null ) loader = handler;
+
+			}
+
+			return this.detectSupport().then( function ( isSupported ) {
+
+				if ( isSupported ) return parser.loadTextureImage( textureIndex, source, loader );
+
+				if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) {
+
+					throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' );
+
+				} // Fall back to PNG or JPEG.
+
+
+				return parser.loadTexture( textureIndex );
+
+			} );
+
+		};
+
+		GLTFTextureWebPExtension.prototype.detectSupport = function () {
+
+			if ( ! this.isSupported ) {
+
+				this.isSupported = new Promise( function ( resolve ) {
+
+					var image = new Image(); // Lossy test image. Support for lossy images doesn't guarantee support for all
+					// WebP images, unfortunately.
+
+					image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA';
+
+					image.onload = image.onerror = function () {
+
+						resolve( image.height === 1 );
+
+					};
+
+				} );
+
+			}
+
+			return this.isSupported;
+
+		};
+		/**
+	* meshopt BufferView Compression Extension
+	*
+	* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression
+	*/
+
+
+		function GLTFMeshoptCompression( parser ) {
+
+			this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION;
+			this.parser = parser;
+
+		}
+
+		GLTFMeshoptCompression.prototype.loadBufferView = function ( index ) {
+
+			var json = this.parser.json;
+			var bufferView = json.bufferViews[ index ];
+
+			if ( bufferView.extensions && bufferView.extensions[ this.name ] ) {
+
+				var extensionDef = bufferView.extensions[ this.name ];
+				var buffer = this.parser.getDependency( 'buffer', extensionDef.buffer );
+				var decoder = this.parser.options.meshoptDecoder;
+
+				if ( ! decoder || ! decoder.supported ) {
+
+					if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) {
+
+						throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' );
+
+					} else {
+
+						// Assumes that the extension is optional and that fallback buffer data is present
+						return null;
+
+					}
+
+				}
+
+				return Promise.all( [ buffer, decoder.ready ] ).then( function ( res ) {
+
+					var byteOffset = extensionDef.byteOffset || 0;
+					var byteLength = extensionDef.byteLength || 0;
+					var count = extensionDef.count;
+					var stride = extensionDef.byteStride;
+					var result = new ArrayBuffer( count * stride );
+					var source = new Uint8Array( res[ 0 ], byteOffset, byteLength );
+					decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter );
+					return result;
+
+				} );
+
+			} else {
+
+				return null;
+
+			}
+
+		};
+		/* BINARY EXTENSION */
+
+
+		var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
+		var BINARY_EXTENSION_HEADER_LENGTH = 12;
+		var BINARY_EXTENSION_CHUNK_TYPES = {
+			JSON: 0x4E4F534A,
+			BIN: 0x004E4942
+		};
+
+		function GLTFBinaryExtension( data ) {
+
+			this.name = EXTENSIONS.KHR_BINARY_GLTF;
+			this.content = null;
+			this.body = null;
+			var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );
+			this.header = {
+				magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ),
+				version: headerView.getUint32( 4, true ),
+				length: headerView.getUint32( 8, true )
+			};
+
+			if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) {
+
+				throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' );
+
+			} else if ( this.header.version < 2.0 ) {
+
+				throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' );
+
+			}
+
+			var chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH;
+			var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH );
+			var chunkIndex = 0;
+
+			while ( chunkIndex < chunkContentsLength ) {
+
+				var chunkLength = chunkView.getUint32( chunkIndex, true );
+				chunkIndex += 4;
+				var chunkType = chunkView.getUint32( chunkIndex, true );
+				chunkIndex += 4;
+
+				if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) {
+
+					var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength );
+					this.content = THREE.LoaderUtils.decodeText( contentArray );
+
+				} else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) {
+
+					var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
+					this.body = data.slice( byteOffset, byteOffset + chunkLength );
+
+				} // Clients must ignore chunks with unknown types.
+
+
+				chunkIndex += chunkLength;
+
+			}
+
+			if ( this.content === null ) {
+
+				throw new Error( 'THREE.GLTFLoader: JSON content not found.' );
+
+			}
+
+		}
+		/**
+	 * DRACO THREE.Mesh Compression Extension
+	 *
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression
+	 */
+
+
+		function GLTFDracoMeshCompressionExtension( json, dracoLoader ) {
+
+			if ( ! dracoLoader ) {
+
+				throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' );
+
+			}
+
+			this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
+			this.json = json;
+			this.dracoLoader = dracoLoader;
+			this.dracoLoader.preload();
+
+		}
+
+		GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) {
+
+			var json = this.json;
+			var dracoLoader = this.dracoLoader;
+			var bufferViewIndex = primitive.extensions[ this.name ].bufferView;
+			var gltfAttributeMap = primitive.extensions[ this.name ].attributes;
+			var threeAttributeMap = {};
+			var attributeNormalizedMap = {};
+			var attributeTypeMap = {};
+
+			for ( var attributeName in gltfAttributeMap ) {
+
+				var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
+				threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ];
+
+			}
+
+			for ( attributeName in primitive.attributes ) {
+
+				var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();
+
+				if ( gltfAttributeMap[ attributeName ] !== undefined ) {
+
+					var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
+					var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];
+					attributeTypeMap[ threeAttributeName ] = componentType;
+					attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true;
+
+				}
+
+			}
+
+			return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {
+
+				return new Promise( function ( resolve ) {
+
+					dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {
+
+						for ( var attributeName in geometry.attributes ) {
+
+							var attribute = geometry.attributes[ attributeName ];
+							var normalized = attributeNormalizedMap[ attributeName ];
+							if ( normalized !== undefined ) attribute.normalized = normalized;
+
+						}
+
+						resolve( geometry );
+
+					}, threeAttributeMap, attributeTypeMap );
+
+				} );
+
+			} );
+
+		};
+		/**
+	 * Texture Transform Extension
+	 *
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform
+	 */
+
+
+		function GLTFTextureTransformExtension() {
+
+			this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;
+
+		}
+
+		GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) {
+
+			texture = texture.clone();
+
+			if ( transform.offset !== undefined ) {
+
+				texture.offset.fromArray( transform.offset );
+
+			}
+
+			if ( transform.rotation !== undefined ) {
+
+				texture.rotation = transform.rotation;
+
+			}
+
+			if ( transform.scale !== undefined ) {
+
+				texture.repeat.fromArray( transform.scale );
+
+			}
+
+			if ( transform.texCoord !== undefined ) {
+
+				console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' );
+
+			}
+
+			texture.needsUpdate = true;
+			return texture;
+
+		};
+		/**
+	 * Specular-Glossiness Extension
+	 *
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
+	 */
+
+		/**
+	 * A sub class of StandardMaterial with some of the functionality
+	 * changed via the `onBeforeCompile` callback
+	 * @pailhead
+	 */
+
+
+		function GLTFMeshStandardSGMaterial( params ) {
+
+			THREE.MeshStandardMaterial.call( this );
+			this.isGLTFSpecularGlossinessMaterial = true; //various chunks that need replacing
+
+			var specularMapParsFragmentChunk = [ '#ifdef USE_SPECULARMAP', '	uniform sampler2D specularMap;', '#endif' ].join( '\n' );
+			var glossinessMapParsFragmentChunk = [ '#ifdef USE_GLOSSINESSMAP', '	uniform sampler2D glossinessMap;', '#endif' ].join( '\n' );
+			var specularMapFragmentChunk = [ 'vec3 specularFactor = specular;', '#ifdef USE_SPECULARMAP', '	vec4 texelSpecular = texture2D( specularMap, vUv );', '	texelSpecular = sRGBToLinear( texelSpecular );', '	// reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', '	specularFactor *= texelSpecular.rgb;', '#endif' ].join( '\n' );
+			var glossinessMapFragmentChunk = [ 'float glossinessFactor = glossiness;', '#ifdef USE_GLOSSINESSMAP', '	vec4 texelGlossiness = texture2D( glossinessMap, vUv );', '	// reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', '	glossinessFactor *= texelGlossiness.a;', '#endif' ].join( '\n' );
+			var lightPhysicalFragmentChunk = [ 'PhysicalMaterial material;', 'material.diffuseColor = diffuseColor.rgb * ( 1. - max( specularFactor.r, max( specularFactor.g, specularFactor.b ) ) );', 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );', 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );', 'material.specularRoughness = max( 1.0 - glossinessFactor, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.', 'material.specularRoughness += geometryRoughness;', 'material.specularRoughness = min( material.specularRoughness, 1.0 );', 'material.specularColor = specularFactor;' ].join( '\n' );
+			var uniforms = {
+				specular: {
+					value: new THREE.Color().setHex( 0xffffff )
+				},
+				glossiness: {
+					value: 1
+				},
+				specularMap: {
+					value: null
+				},
+				glossinessMap: {
+					value: null
+				}
+			};
+			this._extraUniforms = uniforms;
+
+			this.onBeforeCompile = function ( shader ) {
+
+				for ( var uniformName in uniforms ) {
+
+					shader.uniforms[ uniformName ] = uniforms[ uniformName ];
+
+				}
+
+				shader.fragmentShader = shader.fragmentShader.replace( 'uniform float roughness;', 'uniform vec3 specular;' ).replace( 'uniform float metalness;', 'uniform float glossiness;' ).replace( '#include <roughnessmap_pars_fragment>', specularMapParsFragmentChunk ).replace( '#include <metalnessmap_pars_fragment>', glossinessMapParsFragmentChunk ).replace( '#include <roughnessmap_fragment>', specularMapFragmentChunk ).replace( '#include <metalnessmap_fragment>', glossinessMapFragmentChunk ).replace( '#include <lights_physical_fragment>', lightPhysicalFragmentChunk );
+
+			};
+
+			Object.defineProperties( this, {
+				specular: {
+					get: function () {
+
+						return uniforms.specular.value;
+
+					},
+					set: function ( v ) {
+
+						uniforms.specular.value = v;
+
+					}
+				},
+				specularMap: {
+					get: function () {
+
+						return uniforms.specularMap.value;
+
+					},
+					set: function ( v ) {
+
+						uniforms.specularMap.value = v;
+
+						if ( v ) {
+
+							this.defines.USE_SPECULARMAP = ''; // USE_UV is set by the renderer for specular maps
+
+						} else {
+
+							delete this.defines.USE_SPECULARMAP;
+
+						}
+
+					}
+				},
+				glossiness: {
+					get: function () {
+
+						return uniforms.glossiness.value;
+
+					},
+					set: function ( v ) {
+
+						uniforms.glossiness.value = v;
+
+					}
+				},
+				glossinessMap: {
+					get: function () {
+
+						return uniforms.glossinessMap.value;
+
+					},
+					set: function ( v ) {
+
+						uniforms.glossinessMap.value = v;
+
+						if ( v ) {
+
+							this.defines.USE_GLOSSINESSMAP = '';
+							this.defines.USE_UV = '';
+
+						} else {
+
+							delete this.defines.USE_GLOSSINESSMAP;
+							delete this.defines.USE_UV;
+
+						}
+
+					}
+				}
+			} );
+			delete this.metalness;
+			delete this.roughness;
+			delete this.metalnessMap;
+			delete this.roughnessMap;
+			this.setValues( params );
+
+		}
+
+		GLTFMeshStandardSGMaterial.prototype = Object.create( THREE.MeshStandardMaterial.prototype );
+		GLTFMeshStandardSGMaterial.prototype.constructor = GLTFMeshStandardSGMaterial;
+
+		GLTFMeshStandardSGMaterial.prototype.copy = function ( source ) {
+
+			THREE.MeshStandardMaterial.prototype.copy.call( this, source );
+			this.specularMap = source.specularMap;
+			this.specular.copy( source.specular );
+			this.glossinessMap = source.glossinessMap;
+			this.glossiness = source.glossiness;
+			delete this.metalness;
+			delete this.roughness;
+			delete this.metalnessMap;
+			delete this.roughnessMap;
+			return this;
+
+		};
+
+		function GLTFMaterialsPbrSpecularGlossinessExtension() {
+
+			return {
+				name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,
+				specularGlossinessParams: [ 'color', 'map', 'lightMap', 'lightMapIntensity', 'aoMap', 'aoMapIntensity', 'emissive', 'emissiveIntensity', 'emissiveMap', 'bumpMap', 'bumpScale', 'normalMap', 'normalMapType', 'displacementMap', 'displacementScale', 'displacementBias', 'specularMap', 'specular', 'glossinessMap', 'glossiness', 'alphaMap', 'envMap', 'envMapIntensity', 'refractionRatio' ],
+				getMaterialType: function () {
+
+					return GLTFMeshStandardSGMaterial;
+
+				},
+				extendParams: function ( materialParams, materialDef, parser ) {
+
+					var pbrSpecularGlossiness = materialDef.extensions[ this.name ];
+					materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
+					materialParams.opacity = 1.0;
+					var pending = [];
+
+					if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) {
+
+						var array = pbrSpecularGlossiness.diffuseFactor;
+						materialParams.color.fromArray( array );
+						materialParams.opacity = array[ 3 ];
+
+					}
+
+					if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) {
+
+						pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) );
+
+					}
+
+					materialParams.emissive = new THREE.Color( 0.0, 0.0, 0.0 );
+					materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
+					materialParams.specular = new THREE.Color( 1.0, 1.0, 1.0 );
+
+					if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) {
+
+						materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor );
+
+					}
+
+					if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) {
+
+						var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture;
+						pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) );
+						pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) );
+
+					}
+
+					return Promise.all( pending );
+
+				},
+				createMaterial: function ( materialParams ) {
+
+					var material = new GLTFMeshStandardSGMaterial( materialParams );
+					material.fog = true;
+					material.color = materialParams.color;
+					material.map = materialParams.map === undefined ? null : materialParams.map;
+					material.lightMap = null;
+					material.lightMapIntensity = 1.0;
+					material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap;
+					material.aoMapIntensity = 1.0;
+					material.emissive = materialParams.emissive;
+					material.emissiveIntensity = 1.0;
+					material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap;
+					material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap;
+					material.bumpScale = 1;
+					material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap;
+					material.normalMapType = THREE.TangentSpaceNormalMap;
+					if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale;
+					material.displacementMap = null;
+					material.displacementScale = 1;
+					material.displacementBias = 0;
+					material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap;
+					material.specular = materialParams.specular;
+					material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap;
+					material.glossiness = materialParams.glossiness;
+					material.alphaMap = null;
+					material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap;
+					material.envMapIntensity = 1.0;
+					material.refractionRatio = 0.98;
+					return material;
+
+				}
+			};
+
+		}
+		/**
+	 * THREE.Mesh Quantization Extension
+	 *
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization
+	 */
+
+
+		function GLTFMeshQuantizationExtension() {
+
+			this.name = EXTENSIONS.KHR_MESH_QUANTIZATION;
+
+		}
+		/*********************************/
+
+		/********** INTERPOLATION ********/
+
+		/*********************************/
+		// Spline Interpolation
+		// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
+
+
+		function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+			THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+		}
+
+		GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype );
+		GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant;
+
+		GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) {
+
+			// Copies a sample value to the result buffer. See description of glTF
+			// CUBICSPLINE values layout in interpolate_() function below.
+			var result = this.resultBuffer,
+				values = this.sampleValues,
+				valueSize = this.valueSize,
+				offset = index * valueSize * 3 + valueSize;
+
+			for ( var i = 0; i !== valueSize; i ++ ) {
+
+				result[ i ] = values[ offset + i ];
+
+			}
+
+			return result;
+
+		};
+
+		GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
+		GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
+
+		GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
+
+			var result = this.resultBuffer;
+			var values = this.sampleValues;
+			var stride = this.valueSize;
+			var stride2 = stride * 2;
+			var stride3 = stride * 3;
+			var td = t1 - t0;
+			var p = ( t - t0 ) / td;
+			var pp = p * p;
+			var ppp = pp * p;
+			var offset1 = i1 * stride3;
+			var offset0 = offset1 - stride3;
+			var s2 = - 2 * ppp + 3 * pp;
+			var s3 = ppp - pp;
+			var s0 = 1 - s2;
+			var s1 = s3 - pp + p; // Layout of keyframe output values for CUBICSPLINE animations:
+			//	 [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
+
+			for ( var i = 0; i !== stride; i ++ ) {
+
+				var p0 = values[ offset0 + i + stride ]; // splineVertex_k
+
+				var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
+
+				var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
+
+				var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
+
+				result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
+
+			}
+
+			return result;
+
+		};
+		/*********************************/
+
+		/********** INTERNALS ************/
+
+		/*********************************/
+
+		/* CONSTANTS */
+
+
+		var WEBGL_CONSTANTS = {
+			FLOAT: 5126,
+			//FLOAT_MAT2: 35674,
+			FLOAT_MAT3: 35675,
+			FLOAT_MAT4: 35676,
+			FLOAT_VEC2: 35664,
+			FLOAT_VEC3: 35665,
+			FLOAT_VEC4: 35666,
+			LINEAR: 9729,
+			REPEAT: 10497,
+			SAMPLER_2D: 35678,
+			POINTS: 0,
+			LINES: 1,
+			LINE_LOOP: 2,
+			LINE_STRIP: 3,
+			TRIANGLES: 4,
+			TRIANGLE_STRIP: 5,
+			TRIANGLE_FAN: 6,
+			UNSIGNED_BYTE: 5121,
+			UNSIGNED_SHORT: 5123
+		};
+		var WEBGL_COMPONENT_TYPES = {
+			5120: Int8Array,
+			5121: Uint8Array,
+			5122: Int16Array,
+			5123: Uint16Array,
+			5125: Uint32Array,
+			5126: Float32Array
+		};
+		var WEBGL_FILTERS = {
+			9728: THREE.NearestFilter,
+			9729: THREE.LinearFilter,
+			9984: THREE.NearestMipmapNearestFilter,
+			9985: THREE.LinearMipmapNearestFilter,
+			9986: THREE.NearestMipmapLinearFilter,
+			9987: THREE.LinearMipmapLinearFilter
+		};
+		var WEBGL_WRAPPINGS = {
+			33071: THREE.ClampToEdgeWrapping,
+			33648: THREE.MirroredRepeatWrapping,
+			10497: THREE.RepeatWrapping
+		};
+		var WEBGL_TYPE_SIZES = {
+			'SCALAR': 1,
+			'VEC2': 2,
+			'VEC3': 3,
+			'VEC4': 4,
+			'MAT2': 4,
+			'MAT3': 9,
+			'MAT4': 16
+		};
+		var ATTRIBUTES = {
+			POSITION: 'position',
+			NORMAL: 'normal',
+			TANGENT: 'tangent',
+			TEXCOORD_0: 'uv',
+			TEXCOORD_1: 'uv2',
+			COLOR_0: 'color',
+			WEIGHTS_0: 'skinWeight',
+			JOINTS_0: 'skinIndex'
+		};
+		var PATH_PROPERTIES = {
+			scale: 'scale',
+			translation: 'position',
+			rotation: 'quaternion',
+			weights: 'morphTargetInfluences'
+		};
+		var INTERPOLATION = {
+			CUBICSPLINE: undefined,
+			// We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
+			// keyframe track will be initialized with a default interpolation type, then modified.
+			LINEAR: THREE.InterpolateLinear,
+			STEP: THREE.InterpolateDiscrete
+		};
+		var ALPHA_MODES = {
+			OPAQUE: 'OPAQUE',
+			MASK: 'MASK',
+			BLEND: 'BLEND'
+		};
+		/* UTILITY FUNCTIONS */
+
+		function resolveURL( url, path ) {
+
+			// Invalid URL
+			if ( typeof url !== 'string' || url === '' ) return ''; // Host Relative URL
+
+			if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) {
+
+				path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' );
+
+			} // Absolute URL http://,https://,//
+
+
+			if ( /^(https?:)?\/\//i.test( url ) ) return url; // Data URI
+
+			if ( /^data:.*,.*$/i.test( url ) ) return url; // Blob URL
+
+			if ( /^blob:.*$/i.test( url ) ) return url; // Relative URL
+
+			return path + url;
+
+		}
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
+	 */
+
+
+		function createDefaultMaterial( cache ) {
+
+			if ( cache[ 'DefaultMaterial' ] === undefined ) {
+
+				cache[ 'DefaultMaterial' ] = new THREE.MeshStandardMaterial( {
+					color: 0xFFFFFF,
+					emissive: 0x000000,
+					metalness: 1,
+					roughness: 1,
+					transparent: false,
+					depthTest: true,
+					side: THREE.FrontSide
+				} );
+
+			}
+
+			return cache[ 'DefaultMaterial' ];
+
+		}
+
+		function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {
+
+			// Add unknown glTF extensions to an object's userData.
+			for ( var name in objectDef.extensions ) {
+
+				if ( knownExtensions[ name ] === undefined ) {
+
+					object.userData.gltfExtensions = object.userData.gltfExtensions || {};
+					object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ];
+
+				}
+
+			}
+
+		}
+		/**
+	 * @param {Object3D|Material|BufferGeometry} object
+	 * @param {GLTF.definition} gltfDef
+	 */
+
+
+		function assignExtrasToUserData( object, gltfDef ) {
+
+			if ( gltfDef.extras !== undefined ) {
+
+				if ( typeof gltfDef.extras === 'object' ) {
+
+					Object.assign( object.userData, gltfDef.extras );
+
+				} else {
+
+					console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras );
+
+				}
+
+			}
+
+		}
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
+	 *
+	 * @param {BufferGeometry} geometry
+	 * @param {Array<GLTF.Target>} targets
+	 * @param {GLTFParser} parser
+	 * @return {Promise<BufferGeometry>}
+	 */
+
+
+		function addMorphTargets( geometry, targets, parser ) {
+
+			var hasMorphPosition = false;
+			var hasMorphNormal = false;
+
+			for ( var i = 0, il = targets.length; i < il; i ++ ) {
+
+				var target = targets[ i ];
+				if ( target.POSITION !== undefined ) hasMorphPosition = true;
+				if ( target.NORMAL !== undefined ) hasMorphNormal = true;
+				if ( hasMorphPosition && hasMorphNormal ) break;
+
+			}
+
+			if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry );
+			var pendingPositionAccessors = [];
+			var pendingNormalAccessors = [];
+
+			for ( var i = 0, il = targets.length; i < il; i ++ ) {
+
+				var target = targets[ i ];
+
+				if ( hasMorphPosition ) {
+
+					var pendingAccessor = target.POSITION !== undefined ? parser.getDependency( 'accessor', target.POSITION ) : geometry.attributes.position;
+					pendingPositionAccessors.push( pendingAccessor );
+
+				}
+
+				if ( hasMorphNormal ) {
+
+					var pendingAccessor = target.NORMAL !== undefined ? parser.getDependency( 'accessor', target.NORMAL ) : geometry.attributes.normal;
+					pendingNormalAccessors.push( pendingAccessor );
+
+				}
+
+			}
+
+			return Promise.all( [ Promise.all( pendingPositionAccessors ), Promise.all( pendingNormalAccessors ) ] ).then( function ( accessors ) {
+
+				var morphPositions = accessors[ 0 ];
+				var morphNormals = accessors[ 1 ];
+				if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
+				if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
+				geometry.morphTargetsRelative = true;
+				return geometry;
+
+			} );
+
+		}
+		/**
+	 * @param {Mesh} mesh
+	 * @param {GLTF.Mesh} meshDef
+	 */
+
+
+		function updateMorphTargets( mesh, meshDef ) {
+
+			mesh.updateMorphTargets();
+
+			if ( meshDef.weights !== undefined ) {
+
+				for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) {
+
+					mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ];
+
+				}
+
+			} // .extras has user-defined data, so check that .extras.targetNames is an array.
+
+
+			if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) {
+
+				var targetNames = meshDef.extras.targetNames;
+
+				if ( mesh.morphTargetInfluences.length === targetNames.length ) {
+
+					mesh.morphTargetDictionary = {};
+
+					for ( var i = 0, il = targetNames.length; i < il; i ++ ) {
+
+						mesh.morphTargetDictionary[ targetNames[ i ] ] = i;
+
+					}
+
+				} else {
+
+					console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' );
+
+				}
+
+			}
+
+		}
+
+		function createPrimitiveKey( primitiveDef ) {
+
+			var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
+			var geometryKey;
+
+			if ( dracoExtension ) {
+
+				geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices + ':' + createAttributesKey( dracoExtension.attributes );
+
+			} else {
+
+				geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode;
+
+			}
+
+			return geometryKey;
+
+		}
+
+		function createAttributesKey( attributes ) {
+
+			var attributesKey = '';
+			var keys = Object.keys( attributes ).sort();
+
+			for ( var i = 0, il = keys.length; i < il; i ++ ) {
+
+				attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';';
+
+			}
+
+			return attributesKey;
+
+		}
+
+		function getNormalizedComponentScale( constructor ) {
+
+			// Reference:
+			// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data
+			switch ( constructor ) {
+
+				case Int8Array:
+					return 1 / 127;
+
+				case Uint8Array:
+					return 1 / 255;
+
+				case Int16Array:
+					return 1 / 32767;
+
+				case Uint16Array:
+					return 1 / 65535;
+
+				default:
+					throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' );
+
+			}
+
+		}
+		/* GLTF PARSER */
+
+
+		function GLTFParser( json, options ) {
+
+			this.json = json || {};
+			this.extensions = {};
+			this.plugins = {};
+			this.options = options || {}; // loader object cache
+
+			this.cache = new GLTFRegistry(); // associations between Three.js objects and glTF elements
+
+			this.associations = new Map(); // THREE.BufferGeometry caching
+
+			this.primitiveCache = {}; // THREE.Object3D instance caches
+
+			this.meshCache = {
+				refs: {},
+				uses: {}
+			};
+			this.cameraCache = {
+				refs: {},
+				uses: {}
+			};
+			this.lightCache = {
+				refs: {},
+				uses: {}
+			}; // Track node names, to ensure no duplicates
+
+			this.nodeNamesUsed = {}; // Use an THREE.ImageBitmapLoader if imageBitmaps are supported. Moves much of the
+			// expensive work of uploading a texture to the GPU off the main thread.
+
+			if ( typeof createImageBitmap !== 'undefined' && /Firefox/.test( navigator.userAgent ) === false ) {
+
+				this.textureLoader = new THREE.ImageBitmapLoader( this.options.manager );
+
+			} else {
+
+				this.textureLoader = new THREE.TextureLoader( this.options.manager );
+
+			}
+
+			this.textureLoader.setCrossOrigin( this.options.crossOrigin );
+			this.textureLoader.setRequestHeader( this.options.requestHeader );
+			this.fileLoader = new THREE.FileLoader( this.options.manager );
+			this.fileLoader.setResponseType( 'arraybuffer' );
+
+			if ( this.options.crossOrigin === 'use-credentials' ) {
+
+				this.fileLoader.setWithCredentials( true );
+
+			}
+
+		}
+
+		GLTFParser.prototype.setExtensions = function ( extensions ) {
+
+			this.extensions = extensions;
+
+		};
+
+		GLTFParser.prototype.setPlugins = function ( plugins ) {
+
+			this.plugins = plugins;
+
+		};
+
+		GLTFParser.prototype.parse = function ( onLoad, onError ) {
+
+			var parser = this;
+			var json = this.json;
+			var extensions = this.extensions; // Clear the loader cache
+
+			this.cache.removeAll(); // Mark the special nodes/meshes in json for efficient parse
+
+			this._invokeAll( function ( ext ) {
+
+				return ext._markDefs && ext._markDefs();
+
+			} );
+
+			Promise.all( this._invokeAll( function ( ext ) {
+
+				return ext.beforeRoot && ext.beforeRoot();
+
+			} ) ).then( function () {
+
+				return Promise.all( [ parser.getDependencies( 'scene' ), parser.getDependencies( 'animation' ), parser.getDependencies( 'camera' ) ] );
+
+			} ).then( function ( dependencies ) {
+
+				var result = {
+					scene: dependencies[ 0 ][ json.scene || 0 ],
+					scenes: dependencies[ 0 ],
+					animations: dependencies[ 1 ],
+					cameras: dependencies[ 2 ],
+					asset: json.asset,
+					parser: parser,
+					userData: {}
+				};
+				addUnknownExtensionsToUserData( extensions, result, json );
+				assignExtrasToUserData( result, json );
+				Promise.all( parser._invokeAll( function ( ext ) {
+
+					return ext.afterRoot && ext.afterRoot( result );
+
+				} ) ).then( function () {
+
+					onLoad( result );
+
+				} );
+
+			} ).catch( onError );
+
+		};
+		/**
+	 * Marks the special nodes/meshes in json for efficient parse.
+	 */
+
+
+		GLTFParser.prototype._markDefs = function () {
+
+			var nodeDefs = this.json.nodes || [];
+			var skinDefs = this.json.skins || [];
+			var meshDefs = this.json.meshes || []; // Nothing in the node definition indicates whether it is a THREE.Bone or an
+			// THREE.Object3D. Use the skins' joint references to mark bones.
+
+			for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) {
+
+				var joints = skinDefs[ skinIndex ].joints;
+
+				for ( var i = 0, il = joints.length; i < il; i ++ ) {
+
+					nodeDefs[ joints[ i ] ].isBone = true;
+
+				}
+
+			} // Iterate over all nodes, marking references to shared resources,
+			// as well as skeleton joints.
+
+
+			for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {
+
+				var nodeDef = nodeDefs[ nodeIndex ];
+
+				if ( nodeDef.mesh !== undefined ) {
+
+					this._addNodeRef( this.meshCache, nodeDef.mesh ); // Nothing in the mesh definition indicates whether it is
+					// a THREE.SkinnedMesh or THREE.Mesh. Use the node's mesh reference
+					// to mark THREE.SkinnedMesh if node has skin.
+
+
+					if ( nodeDef.skin !== undefined ) {
+
+						meshDefs[ nodeDef.mesh ].isSkinnedMesh = true;
+
+					}
+
+				}
+
+				if ( nodeDef.camera !== undefined ) {
+
+					this._addNodeRef( this.cameraCache, nodeDef.camera );
+
+				}
+
+			}
+
+		};
+		/**
+	 * Counts references to shared node / THREE.Object3D resources. These resources
+	 * can be reused, or "instantiated", at multiple nodes in the scene
+	 * hierarchy. THREE.Mesh, Camera, and Light instances are instantiated and must
+	 * be marked. Non-scenegraph resources (like Materials, Geometries, and
+	 * Textures) can be reused directly and are not marked here.
+	 *
+	 * Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
+	 */
+
+
+		GLTFParser.prototype._addNodeRef = function ( cache, index ) {
+
+			if ( index === undefined ) return;
+
+			if ( cache.refs[ index ] === undefined ) {
+
+				cache.refs[ index ] = cache.uses[ index ] = 0;
+
+			}
+
+			cache.refs[ index ] ++;
+
+		};
+		/** Returns a reference to a shared resource, cloning it if necessary. */
+
+
+		GLTFParser.prototype._getNodeRef = function ( cache, index, object ) {
+
+			if ( cache.refs[ index ] <= 1 ) return object;
+			var ref = object.clone();
+			ref.name += '_instance_' + cache.uses[ index ] ++;
+			return ref;
+
+		};
+
+		GLTFParser.prototype._invokeOne = function ( func ) {
+
+			var extensions = Object.values( this.plugins );
+			extensions.push( this );
+
+			for ( var i = 0; i < extensions.length; i ++ ) {
+
+				var result = func( extensions[ i ] );
+				if ( result ) return result;
+
+			}
+
+			return null;
+
+		};
+
+		GLTFParser.prototype._invokeAll = function ( func ) {
+
+			var extensions = Object.values( this.plugins );
+			extensions.unshift( this );
+			var pending = [];
+
+			for ( var i = 0; i < extensions.length; i ++ ) {
+
+				var result = func( extensions[ i ] );
+				if ( result ) pending.push( result );
+
+			}
+
+			return pending;
+
+		};
+		/**
+	 * Requests the specified dependency asynchronously, with caching.
+	 * @param {string} type
+	 * @param {number} index
+	 * @return {Promise<Object3D|Material|THREE.Texture|AnimationClip|ArrayBuffer|Object>}
+	 */
+
+
+		GLTFParser.prototype.getDependency = function ( type, index ) {
+
+			var cacheKey = type + ':' + index;
+			var dependency = this.cache.get( cacheKey );
+
+			if ( ! dependency ) {
+
+				switch ( type ) {
+
+					case 'scene':
+						dependency = this.loadScene( index );
+						break;
+
+					case 'node':
+						dependency = this.loadNode( index );
+						break;
+
+					case 'mesh':
+						dependency = this._invokeOne( function ( ext ) {
+
+							return ext.loadMesh && ext.loadMesh( index );
+
+						} );
+						break;
+
+					case 'accessor':
+						dependency = this.loadAccessor( index );
+						break;
+
+					case 'bufferView':
+						dependency = this._invokeOne( function ( ext ) {
+
+							return ext.loadBufferView && ext.loadBufferView( index );
+
+						} );
+						break;
+
+					case 'buffer':
+						dependency = this.loadBuffer( index );
+						break;
+
+					case 'material':
+						dependency = this._invokeOne( function ( ext ) {
+
+							return ext.loadMaterial && ext.loadMaterial( index );
+
+						} );
+						break;
+
+					case 'texture':
+						dependency = this._invokeOne( function ( ext ) {
+
+							return ext.loadTexture && ext.loadTexture( index );
+
+						} );
+						break;
+
+					case 'skin':
+						dependency = this.loadSkin( index );
+						break;
+
+					case 'animation':
+						dependency = this.loadAnimation( index );
+						break;
+
+					case 'camera':
+						dependency = this.loadCamera( index );
+						break;
+
+					default:
+						throw new Error( 'Unknown type: ' + type );
+
+				}
+
+				this.cache.add( cacheKey, dependency );
+
+			}
+
+			return dependency;
+
+		};
+		/**
+	 * Requests all dependencies of the specified type asynchronously, with caching.
+	 * @param {string} type
+	 * @return {Promise<Array<Object>>}
+	 */
+
+
+		GLTFParser.prototype.getDependencies = function ( type ) {
+
+			var dependencies = this.cache.get( type );
+
+			if ( ! dependencies ) {
+
+				var parser = this;
+				var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || [];
+				dependencies = Promise.all( defs.map( function ( def, index ) {
+
+					return parser.getDependency( type, index );
+
+				} ) );
+				this.cache.add( type, dependencies );
+
+			}
+
+			return dependencies;
+
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
+	 * @param {number} bufferIndex
+	 * @return {Promise<ArrayBuffer>}
+	 */
+
+
+		GLTFParser.prototype.loadBuffer = function ( bufferIndex ) {
+
+			var bufferDef = this.json.buffers[ bufferIndex ];
+			var loader = this.fileLoader;
+
+			if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) {
+
+				throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' );
+
+			} // If present, GLB container is required to be the first buffer.
+
+
+			if ( bufferDef.uri === undefined && bufferIndex === 0 ) {
+
+				return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body );
+
+			}
+
+			var options = this.options;
+			return new Promise( function ( resolve, reject ) {
+
+				loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () {
+
+					reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) );
+
+				} );
+
+			} );
+
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
+	 * @param {number} bufferViewIndex
+	 * @return {Promise<ArrayBuffer>}
+	 */
+
+
+		GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) {
+
+			var bufferViewDef = this.json.bufferViews[ bufferViewIndex ];
+			return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) {
+
+				var byteLength = bufferViewDef.byteLength || 0;
+				var byteOffset = bufferViewDef.byteOffset || 0;
+				return buffer.slice( byteOffset, byteOffset + byteLength );
+
+			} );
+
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
+	 * @param {number} accessorIndex
+	 * @return {Promise<BufferAttribute|InterleavedBufferAttribute>}
+	 */
+
+
+		GLTFParser.prototype.loadAccessor = function ( accessorIndex ) {
+
+			var parser = this;
+			var json = this.json;
+			var accessorDef = this.json.accessors[ accessorIndex ];
+
+			if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) {
+
+				// Ignore empty accessors, which may be used to declare runtime
+				// information about attributes coming from another source (e.g. Draco
+				// compression extension).
+				return Promise.resolve( null );
+
+			}
+
+			var pendingBufferViews = [];
+
+			if ( accessorDef.bufferView !== undefined ) {
+
+				pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) );
+
+			} else {
+
+				pendingBufferViews.push( null );
+
+			}
+
+			if ( accessorDef.sparse !== undefined ) {
+
+				pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) );
+				pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) );
+
+			}
+
+			return Promise.all( pendingBufferViews ).then( function ( bufferViews ) {
+
+				var bufferView = bufferViews[ 0 ];
+				var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ];
+				var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
+
+				var elementBytes = TypedArray.BYTES_PER_ELEMENT;
+				var itemBytes = elementBytes * itemSize;
+				var byteOffset = accessorDef.byteOffset || 0;
+				var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined;
+				var normalized = accessorDef.normalized === true;
+				var array, bufferAttribute; // The buffer is not interleaved if the stride is the item size in bytes.
+
+				if ( byteStride && byteStride !== itemBytes ) {
+
+					// Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own THREE.InterleavedBuffer
+					// This makes sure that IBA.count reflects accessor.count properly
+					var ibSlice = Math.floor( byteOffset / byteStride );
+					var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count;
+					var ib = parser.cache.get( ibCacheKey );
+
+					if ( ! ib ) {
+
+						array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes ); // Integer parameters to IB/IBA are in array elements, not bytes.
+
+						ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes );
+						parser.cache.add( ibCacheKey, ib );
+
+					}
+
+					bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, byteOffset % byteStride / elementBytes, normalized );
+
+				} else {
+
+					if ( bufferView === null ) {
+
+						array = new TypedArray( accessorDef.count * itemSize );
+
+					} else {
+
+						array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize );
+
+					}
+
+					bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized );
+
+				} // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
+
+
+				if ( accessorDef.sparse !== undefined ) {
+
+					var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
+					var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ];
+					var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
+					var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;
+					var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices );
+					var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize );
+
+					if ( bufferView !== null ) {
+
+						// Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
+						bufferAttribute = new THREE.BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized );
+
+					}
+
+					for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) {
+
+						var index = sparseIndices[ i ];
+						bufferAttribute.setX( index, sparseValues[ i * itemSize ] );
+						if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] );
+						if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] );
+						if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] );
+						if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse THREE.BufferAttribute.' );
+
+					}
+
+				}
+
+				return bufferAttribute;
+
+			} );
+
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
+	 * @param {number} textureIndex
+	 * @return {Promise<THREE.Texture>}
+	 */
+
+
+		GLTFParser.prototype.loadTexture = function ( textureIndex ) {
+
+			var json = this.json;
+			var options = this.options;
+			var textureDef = json.textures[ textureIndex ];
+			var source = json.images[ textureDef.source ];
+			var loader = this.textureLoader;
+
+			if ( source.uri ) {
+
+				var handler = options.manager.getHandler( source.uri );
+				if ( handler !== null ) loader = handler;
+
+			}
+
+			return this.loadTextureImage( textureIndex, source, loader );
+
+		};
+
+		GLTFParser.prototype.loadTextureImage = function ( textureIndex, source, loader ) {
+
+			var parser = this;
+			var json = this.json;
+			var options = this.options;
+			var textureDef = json.textures[ textureIndex ];
+			var URL = self.URL || self.webkitURL;
+			var sourceURI = source.uri;
+			var isObjectURL = false;
+			var hasAlpha = true;
+			if ( source.mimeType === 'image/jpeg' ) hasAlpha = false;
+
+			if ( source.bufferView !== undefined ) {
+
+				// Load binary image data from bufferView, if provided.
+				sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) {
+
+					if ( source.mimeType === 'image/png' ) {
+
+						// Inspect the PNG 'IHDR' chunk to determine whether the image could have an
+						// alpha channel. This check is conservative — the image could have an alpha
+						// channel with all values == 1, and the indexed type (colorType == 3) only
+						// sometimes contains alpha.
+						//
+						// https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header
+						var colorType = new DataView( bufferView, 25, 1 ).getUint8( 0, false );
+						hasAlpha = colorType === 6 || colorType === 4 || colorType === 3;
+
+					}
+
+					isObjectURL = true;
+					var blob = new Blob( [ bufferView ], {
+						type: source.mimeType
+					} );
+					sourceURI = URL.createObjectURL( blob );
+					return sourceURI;
+
+				} );
+
+			} else if ( source.uri === undefined ) {
+
+				throw new Error( 'THREE.GLTFLoader: Image ' + textureIndex + ' is missing URI and bufferView' );
+
+			}
+
+			return Promise.resolve( sourceURI ).then( function ( sourceURI ) {
+
+				return new Promise( function ( resolve, reject ) {
+
+					var onLoad = resolve;
+
+					if ( loader.isImageBitmapLoader === true ) {
+
+						onLoad = function ( imageBitmap ) {
+
+							resolve( new THREE.CanvasTexture( imageBitmap ) );
+
+						};
+
+					}
+
+					loader.load( resolveURL( sourceURI, options.path ), onLoad, undefined, reject );
+
+				} );
+
+			} ).then( function ( texture ) {
+
+				// Clean up resources and configure Texture.
+				if ( isObjectURL === true ) {
+
+					URL.revokeObjectURL( sourceURI );
+
+				}
+
+				texture.flipY = false;
+				if ( textureDef.name ) texture.name = textureDef.name; // When there is definitely no alpha channel in the texture, set THREE.RGBFormat to save space.
+
+				if ( ! hasAlpha ) texture.format = THREE.RGBFormat;
+				var samplers = json.samplers || {};
+				var sampler = samplers[ textureDef.sampler ] || {};
+				texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter;
+				texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipmapLinearFilter;
+				texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping;
+				texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping;
+				parser.associations.set( texture, {
+					type: 'textures',
+					index: textureIndex
+				} );
+				return texture;
+
+			} );
+
+		};
+		/**
+	 * Asynchronously assigns a texture to the given material parameters.
+	 * @param {Object} materialParams
+	 * @param {string} mapName
+	 * @param {Object} mapDef
+	 * @return {Promise}
+	 */
+
+
+		GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) {
+
+			var parser = this;
+			return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) {
+
+				// Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured
+				// However, we will copy UV set 0 to UV set 1 on demand for aoMap
+				if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) {
+
+					console.warn( 'THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' );
+
+				}
+
+				if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) {
+
+					var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined;
+
+					if ( transform ) {
+
+						var gltfReference = parser.associations.get( texture );
+						texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
+						parser.associations.set( texture, gltfReference );
+
+					}
+
+				}
+
+				materialParams[ mapName ] = texture;
+
+			} );
+
+		};
+		/**
+	 * Assigns final material to a THREE.Mesh, THREE.Line, or THREE.Points instance. The instance
+	 * already has a material (generated from the glTF material options alone)
+	 * but reuse of the same glTF material may require multiple threejs materials
+	 * to accommodate different primitive types, defines, etc. New materials will
+	 * be created if necessary, and reused from a cache.
+	 * @param	{Object3D} mesh THREE.Mesh, THREE.Line, or THREE.Points instance.
+	 */
+
+
+		GLTFParser.prototype.assignFinalMaterial = function ( mesh ) {
+
+			var geometry = mesh.geometry;
+			var material = mesh.material;
+			var useVertexTangents = geometry.attributes.tangent !== undefined;
+			var useVertexColors = geometry.attributes.color !== undefined;
+			var useFlatShading = geometry.attributes.normal === undefined;
+			var useSkinning = mesh.isSkinnedMesh === true;
+			var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0;
+			var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
+
+			if ( mesh.isPoints ) {
+
+				var cacheKey = 'PointsMaterial:' + material.uuid;
+				var pointsMaterial = this.cache.get( cacheKey );
+
+				if ( ! pointsMaterial ) {
+
+					pointsMaterial = new THREE.PointsMaterial();
+					THREE.Material.prototype.copy.call( pointsMaterial, material );
+					pointsMaterial.color.copy( material.color );
+					pointsMaterial.map = material.map;
+					pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
+
+					this.cache.add( cacheKey, pointsMaterial );
+
+				}
+
+				material = pointsMaterial;
+
+			} else if ( mesh.isLine ) {
+
+				var cacheKey = 'LineBasicMaterial:' + material.uuid;
+				var lineMaterial = this.cache.get( cacheKey );
+
+				if ( ! lineMaterial ) {
+
+					lineMaterial = new THREE.LineBasicMaterial();
+					THREE.Material.prototype.copy.call( lineMaterial, material );
+					lineMaterial.color.copy( material.color );
+					this.cache.add( cacheKey, lineMaterial );
+
+				}
+
+				material = lineMaterial;
+
+			} // Clone the material if it will be modified
+
+
+			if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) {
+
+				var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
+				if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
+				if ( useSkinning ) cacheKey += 'skinning:';
+				if ( useVertexTangents ) cacheKey += 'vertex-tangents:';
+				if ( useVertexColors ) cacheKey += 'vertex-colors:';
+				if ( useFlatShading ) cacheKey += 'flat-shading:';
+				if ( useMorphTargets ) cacheKey += 'morph-targets:';
+				if ( useMorphNormals ) cacheKey += 'morph-normals:';
+				var cachedMaterial = this.cache.get( cacheKey );
+
+				if ( ! cachedMaterial ) {
+
+					cachedMaterial = material.clone();
+					if ( useSkinning ) cachedMaterial.skinning = true;
+					if ( useVertexColors ) cachedMaterial.vertexColors = true;
+					if ( useFlatShading ) cachedMaterial.flatShading = true;
+					if ( useMorphTargets ) cachedMaterial.morphTargets = true;
+					if ( useMorphNormals ) cachedMaterial.morphNormals = true;
+
+					if ( useVertexTangents ) {
+
+						cachedMaterial.vertexTangents = true; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
+
+						if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1;
+						if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1;
+
+					}
+
+					this.cache.add( cacheKey, cachedMaterial );
+					this.associations.set( cachedMaterial, this.associations.get( material ) );
+
+				}
+
+				material = cachedMaterial;
+
+			} // workarounds for mesh and geometry
+
+
+			if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {
+
+				geometry.setAttribute( 'uv2', geometry.attributes.uv );
+
+			}
+
+			mesh.material = material;
+
+		};
+
+		GLTFParser.prototype.getMaterialType = function ( ) {
+
+			return THREE.MeshStandardMaterial;
+
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
+	 * @param {number} materialIndex
+	 * @return {Promise<Material>}
+	 */
+
+
+		GLTFParser.prototype.loadMaterial = function ( materialIndex ) {
+
+			var parser = this;
+			var json = this.json;
+			var extensions = this.extensions;
+			var materialDef = json.materials[ materialIndex ];
+			var materialType;
+			var materialParams = {};
+			var materialExtensions = materialDef.extensions || {};
+			var pending = [];
+
+			if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) {
+
+				var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ];
+				materialType = sgExtension.getMaterialType();
+				pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) );
+
+			} else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) {
+
+				var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ];
+				materialType = kmuExtension.getMaterialType();
+				pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) );
+
+			} else {
+
+				// Specification:
+				// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
+				var metallicRoughness = materialDef.pbrMetallicRoughness || {};
+				materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 );
+				materialParams.opacity = 1.0;
+
+				if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {
+
+					var array = metallicRoughness.baseColorFactor;
+					materialParams.color.fromArray( array );
+					materialParams.opacity = array[ 3 ];
+
+				}
+
+				if ( metallicRoughness.baseColorTexture !== undefined ) {
+
+					pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) );
+
+				}
+
+				materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
+				materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;
+
+				if ( metallicRoughness.metallicRoughnessTexture !== undefined ) {
+
+					pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) );
+					pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) );
+
+				}
+
+				materialType = this._invokeOne( function ( ext ) {
+
+					return ext.getMaterialType && ext.getMaterialType( materialIndex );
+
+				} );
+				pending.push( Promise.all( this._invokeAll( function ( ext ) {
+
+					return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams );
+
+				} ) ) );
+
+			}
+
+			if ( materialDef.doubleSided === true ) {
+
+				materialParams.side = THREE.DoubleSide;
+
+			}
+
+			var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE;
+
+			if ( alphaMode === ALPHA_MODES.BLEND ) {
+
+				materialParams.transparent = true; // See: https://github.com/mrdoob/three.js/issues/17706
+
+				materialParams.depthWrite = false;
+
+			} else {
+
+				materialParams.transparent = false;
+
+				if ( alphaMode === ALPHA_MODES.MASK ) {
+
+					materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
+
+				}
+
+			}
+
+			if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
+
+				pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
+
+				materialParams.normalScale = new THREE.Vector2( 1, - 1 );
+
+				if ( materialDef.normalTexture.scale !== undefined ) {
+
+					materialParams.normalScale.set( materialDef.normalTexture.scale, - materialDef.normalTexture.scale );
+
+				}
+
+			}
+
+			if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
+
+				pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) );
+
+				if ( materialDef.occlusionTexture.strength !== undefined ) {
+
+					materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;
+
+				}
+
+			}
+
+			if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial ) {
+
+				materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor );
+
+			}
+
+			if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) {
+
+				pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) );
+
+			}
+
+			return Promise.all( pending ).then( function () {
+
+				var material;
+
+				if ( materialType === GLTFMeshStandardSGMaterial ) {
 
-	GLTFMaterialsTransmissionExtension.prototype.getMaterialType = function (materialIndex) {
-		var parser = this.parser;
-		var materialDef = parser.json.materials[materialIndex];
-		if (!materialDef.extensions || !materialDef.extensions[this.name]) return null;
-		return THREE.MeshPhysicalMaterial;
-	};
+					material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams );
 
-	GLTFMaterialsTransmissionExtension.prototype.extendMaterialParams = function (materialIndex, materialParams) {
-		var parser = this.parser;
-		var materialDef = parser.json.materials[materialIndex];
+				} else {
 
-		if (!materialDef.extensions || !materialDef.extensions[this.name]) {
-			return Promise.resolve();
-		}
+					material = new materialType( materialParams );
 
-		var pending = [];
-		var extension = materialDef.extensions[this.name];
+				}
 
-		if (extension.transmissionFactor !== undefined) {
-			materialParams.transmission = extension.transmissionFactor;
-		}
+				if ( materialDef.name ) material.name = materialDef.name; // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding.
 
-		if (extension.transmissionTexture !== undefined) {
-			pending.push(parser.assignTexture(materialParams, 'transmissionMap', extension.transmissionTexture));
-		}
+				if ( material.map ) material.map.encoding = THREE.sRGBEncoding;
+				if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding;
+				assignExtrasToUserData( material, materialDef );
+				parser.associations.set( material, {
+					type: 'materials',
+					index: materialIndex
+				} );
+				if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
+				return material;
 
-		return Promise.all(pending);
-	};
-	/**
-	 * BasisU Texture Extension
-	 *
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu
-	 */
+			} );
 
+		};
+		/** When THREE.Object3D instances are targeted by animation, they need unique names. */
 
-	function GLTFTextureBasisUExtension(parser) {
-		this.parser = parser;
-		this.name = EXTENSIONS.KHR_TEXTURE_BASISU;
-	}
 
-	GLTFTextureBasisUExtension.prototype.loadTexture = function (textureIndex) {
-		var parser = this.parser;
-		var json = parser.json;
-		var textureDef = json.textures[textureIndex];
+		GLTFParser.prototype.createUniqueName = function ( originalName ) {
 
-		if (!textureDef.extensions || !textureDef.extensions[this.name]) {
-			return null;
-		}
+			var sanitizedName = THREE.PropertyBinding.sanitizeNodeName( originalName || '' );
+			var name = sanitizedName;
 
-		var extension = textureDef.extensions[this.name];
-		var source = json.images[extension.source];
-		var loader = parser.options.ktx2Loader;
+			for ( var i = 1; this.nodeNamesUsed[ name ]; ++ i ) {
+
+				name = sanitizedName + '_' + i;
 
-		if (!loader) {
-			if (json.extensionsRequired && json.extensionsRequired.indexOf(this.name) >= 0) {
-				throw new Error('THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures');
-			} else {
-				// Assumes that the extension is optional and that a fallback texture is present
-				return null;
 			}
-		}
 
-		return parser.loadTextureImage(textureIndex, source, loader);
-	};
-	/**
-	 * WebP Texture Extension
-	 *
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp
+			this.nodeNamesUsed[ name ] = true;
+			return name;
+
+		};
+		/**
+	 * @param {BufferGeometry} geometry
+	 * @param {GLTF.Primitive} primitiveDef
+	 * @param {GLTFParser} parser
 	 */
 
 
-	function GLTFTextureWebPExtension(parser) {
-		this.parser = parser;
-		this.name = EXTENSIONS.EXT_TEXTURE_WEBP;
-		this.isSupported = null;
-	}
+		function computeBounds( geometry, primitiveDef, parser ) {
 
-	GLTFTextureWebPExtension.prototype.loadTexture = function (textureIndex) {
-		var name = this.name;
-		var parser = this.parser;
-		var json = parser.json;
-		var textureDef = json.textures[textureIndex];
+			var attributes = primitiveDef.attributes;
+			var box = new THREE.Box3();
 
-		if (!textureDef.extensions || !textureDef.extensions[name]) {
-			return null;
-		}
+			if ( attributes.POSITION !== undefined ) {
 
-		var extension = textureDef.extensions[name];
-		var source = json.images[extension.source];
-		var loader = parser.textureLoader;
+				var accessor = parser.json.accessors[ attributes.POSITION ];
+				var min = accessor.min;
+				var max = accessor.max; // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
 
-		if (source.uri) {
-			var handler = parser.options.manager.getHandler(source.uri);
-			if (handler !== null) loader = handler;
-		}
+				if ( min !== undefined && max !== undefined ) {
 
-		return this.detectSupport().then(function (isSupported) {
-			if (isSupported) return parser.loadTextureImage(textureIndex, source, loader);
+					box.set( new THREE.Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), new THREE.Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) );
 
-			if (json.extensionsRequired && json.extensionsRequired.indexOf(name) >= 0) {
-				throw new Error('THREE.GLTFLoader: WebP required by asset but unsupported.');
-			} // Fall back to PNG or JPEG.
+					if ( accessor.normalized ) {
 
+						var boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] );
+						box.min.multiplyScalar( boxScale );
+						box.max.multiplyScalar( boxScale );
 
-			return parser.loadTexture(textureIndex);
-		});
-	};
+					}
 
-	GLTFTextureWebPExtension.prototype.detectSupport = function () {
-		if (!this.isSupported) {
-			this.isSupported = new Promise(function (resolve) {
-				var image = new Image(); // Lossy test image. Support for lossy images doesn't guarantee support for all
-				// WebP images, unfortunately.
+				} else {
 
-				image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA';
+					console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );
+					return;
 
-				image.onload = image.onerror = function () {
-					resolve(image.height === 1);
-				};
-			});
-		}
+				}
 
-		return this.isSupported;
-	};
-	/**
-	* meshopt BufferView Compression Extension
-	*
-	* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression
-	*/
+			} else {
 
+				return;
 
-	function GLTFMeshoptCompression(parser) {
-		this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION;
-		this.parser = parser;
-	}
+			}
 
-	GLTFMeshoptCompression.prototype.loadBufferView = function (index) {
-		var json = this.parser.json;
-		var bufferView = json.bufferViews[index];
+			var targets = primitiveDef.targets;
 
-		if (bufferView.extensions && bufferView.extensions[this.name]) {
-			var extensionDef = bufferView.extensions[this.name];
-			var buffer = this.parser.getDependency('buffer', extensionDef.buffer);
-			var decoder = this.parser.options.meshoptDecoder;
+			if ( targets !== undefined ) {
 
-			if (!decoder || !decoder.supported) {
-				if (json.extensionsRequired && json.extensionsRequired.indexOf(this.name) >= 0) {
-					throw new Error('THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files');
-				} else {
-					// Assumes that the extension is optional and that fallback buffer data is present
-					return null;
-				}
-			}
+				var maxDisplacement = new THREE.Vector3();
+				var vector = new THREE.Vector3();
 
-			return Promise.all([buffer, decoder.ready]).then(function (res) {
-				var byteOffset = extensionDef.byteOffset || 0;
-				var byteLength = extensionDef.byteLength || 0;
-				var count = extensionDef.count;
-				var stride = extensionDef.byteStride;
-				var result = new ArrayBuffer(count * stride);
-				var source = new Uint8Array(res[0], byteOffset, byteLength);
-				decoder.decodeGltfBuffer(new Uint8Array(result), count, stride, source, extensionDef.mode, extensionDef.filter);
-				return result;
-			});
-		} else {
-			return null;
-		}
-	};
-	/* BINARY EXTENSION */
-
-
-	var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
-	var BINARY_EXTENSION_HEADER_LENGTH = 12;
-	var BINARY_EXTENSION_CHUNK_TYPES = {
-		JSON: 0x4E4F534A,
-		BIN: 0x004E4942
-	};
-
-	function GLTFBinaryExtension(data) {
-		this.name = EXTENSIONS.KHR_BINARY_GLTF;
-		this.content = null;
-		this.body = null;
-		var headerView = new DataView(data, 0, BINARY_EXTENSION_HEADER_LENGTH);
-		this.header = {
-			magic: THREE.LoaderUtils.decodeText(new Uint8Array(data.slice(0, 4))),
-			version: headerView.getUint32(4, true),
-			length: headerView.getUint32(8, true)
-		};
+				for ( var i = 0, il = targets.length; i < il; i ++ ) {
 
-		if (this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC) {
-			throw new Error('THREE.GLTFLoader: Unsupported glTF-Binary header.');
-		} else if (this.header.version < 2.0) {
-			throw new Error('THREE.GLTFLoader: Legacy binary file detected.');
-		}
+					var target = targets[ i ];
 
-		var chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH;
-		var chunkView = new DataView(data, BINARY_EXTENSION_HEADER_LENGTH);
-		var chunkIndex = 0;
+					if ( target.POSITION !== undefined ) {
 
-		while (chunkIndex < chunkContentsLength) {
-			var chunkLength = chunkView.getUint32(chunkIndex, true);
-			chunkIndex += 4;
-			var chunkType = chunkView.getUint32(chunkIndex, true);
-			chunkIndex += 4;
+						var accessor = parser.json.accessors[ target.POSITION ];
+						var min = accessor.min;
+						var max = accessor.max; // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
 
-			if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON) {
-				var contentArray = new Uint8Array(data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength);
-				this.content = THREE.LoaderUtils.decodeText(contentArray);
-			} else if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN) {
-				var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
-				this.body = data.slice(byteOffset, byteOffset + chunkLength);
-			} // Clients must ignore chunks with unknown types.
+						if ( min !== undefined && max !== undefined ) {
 
+							// we need to get max of absolute components because target weight is [-1,1]
+							vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) );
+							vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) );
+							vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) );
 
-			chunkIndex += chunkLength;
-		}
+							if ( accessor.normalized ) {
 
-		if (this.content === null) {
-			throw new Error('THREE.GLTFLoader: JSON content not found.');
-		}
-	}
-	/**
-	 * DRACO THREE.Mesh Compression Extension
-	 *
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression
-	 */
+								var boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] );
+								vector.multiplyScalar( boxScale );
 
+							} // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative
+							// to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets
+							// are used to implement key-frame animations and as such only two are active at a time - this results in very large
+							// boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size.
 
-	function GLTFDracoMeshCompressionExtension(json, dracoLoader) {
-		if (!dracoLoader) {
-			throw new Error('THREE.GLTFLoader: No DRACOLoader instance provided.');
-		}
 
-		this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
-		this.json = json;
-		this.dracoLoader = dracoLoader;
-		this.dracoLoader.preload();
-	}
-
-	GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function (primitive, parser) {
-		var json = this.json;
-		var dracoLoader = this.dracoLoader;
-		var bufferViewIndex = primitive.extensions[this.name].bufferView;
-		var gltfAttributeMap = primitive.extensions[this.name].attributes;
-		var threeAttributeMap = {};
-		var attributeNormalizedMap = {};
-		var attributeTypeMap = {};
-
-		for (var attributeName in gltfAttributeMap) {
-			var threeAttributeName = ATTRIBUTES[attributeName] || attributeName.toLowerCase();
-			threeAttributeMap[threeAttributeName] = gltfAttributeMap[attributeName];
-		}
+							maxDisplacement.max( vector );
 
-		for (attributeName in primitive.attributes) {
-			var threeAttributeName = ATTRIBUTES[attributeName] || attributeName.toLowerCase();
+						} else {
 
-			if (gltfAttributeMap[attributeName] !== undefined) {
-				var accessorDef = json.accessors[primitive.attributes[attributeName]];
-				var componentType = WEBGL_COMPONENT_TYPES[accessorDef.componentType];
-				attributeTypeMap[threeAttributeName] = componentType;
-				attributeNormalizedMap[threeAttributeName] = accessorDef.normalized === true;
-			}
-		}
+							console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );
+
+						}
 
-		return parser.getDependency('bufferView', bufferViewIndex).then(function (bufferView) {
-			return new Promise(function (resolve) {
-				dracoLoader.decodeDracoFile(bufferView, function (geometry) {
-					for (var attributeName in geometry.attributes) {
-						var attribute = geometry.attributes[attributeName];
-						var normalized = attributeNormalizedMap[attributeName];
-						if (normalized !== undefined) attribute.normalized = normalized;
 					}
 
-					resolve(geometry);
-				}, threeAttributeMap, attributeTypeMap);
-			});
-		});
-	};
-	/**
-	 * Texture Transform Extension
-	 *
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform
-	 */
+				} // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets.
 
 
-	function GLTFTextureTransformExtension() {
-		this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;
-	}
+				box.expandByVector( maxDisplacement );
 
-	GLTFTextureTransformExtension.prototype.extendTexture = function (texture, transform) {
-		texture = texture.clone();
+			}
 
-		if (transform.offset !== undefined) {
-			texture.offset.fromArray(transform.offset);
-		}
+			geometry.boundingBox = box;
+			var sphere = new THREE.Sphere();
+			box.getCenter( sphere.center );
+			sphere.radius = box.min.distanceTo( box.max ) / 2;
+			geometry.boundingSphere = sphere;
 
-		if (transform.rotation !== undefined) {
-			texture.rotation = transform.rotation;
 		}
+		/**
+	 * @param {BufferGeometry} geometry
+	 * @param {GLTF.Primitive} primitiveDef
+	 * @param {GLTFParser} parser
+	 * @return {Promise<BufferGeometry>}
+	 */
 
-		if (transform.scale !== undefined) {
-			texture.repeat.fromArray(transform.scale);
-		}
 
-		if (transform.texCoord !== undefined) {
-			console.warn('THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.');
-		}
+		function addPrimitiveAttributes( geometry, primitiveDef, parser ) {
 
-		texture.needsUpdate = true;
-		return texture;
-	};
-	/**
-	 * Specular-Glossiness Extension
-	 *
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
-	 */
+			var attributes = primitiveDef.attributes;
+			var pending = [];
 
-	/**
-	 * A sub class of StandardMaterial with some of the functionality
-	 * changed via the `onBeforeCompile` callback
-	 * @pailhead
-	 */
+			function assignAttributeAccessor( accessorIndex, attributeName ) {
 
+				return parser.getDependency( 'accessor', accessorIndex ).then( function ( accessor ) {
 
-	function GLTFMeshStandardSGMaterial(params) {
-		THREE.MeshStandardMaterial.call(this);
-		this.isGLTFSpecularGlossinessMaterial = true; //various chunks that need replacing
+					geometry.setAttribute( attributeName, accessor );
 
-		var specularMapParsFragmentChunk = ['#ifdef USE_SPECULARMAP', '	uniform sampler2D specularMap;', '#endif'].join('\n');
-		var glossinessMapParsFragmentChunk = ['#ifdef USE_GLOSSINESSMAP', '	uniform sampler2D glossinessMap;', '#endif'].join('\n');
-		var specularMapFragmentChunk = ['vec3 specularFactor = specular;', '#ifdef USE_SPECULARMAP', '	vec4 texelSpecular = texture2D( specularMap, vUv );', '	texelSpecular = sRGBToLinear( texelSpecular );', '	// reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', '	specularFactor *= texelSpecular.rgb;', '#endif'].join('\n');
-		var glossinessMapFragmentChunk = ['float glossinessFactor = glossiness;', '#ifdef USE_GLOSSINESSMAP', '	vec4 texelGlossiness = texture2D( glossinessMap, vUv );', '	// reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', '	glossinessFactor *= texelGlossiness.a;', '#endif'].join('\n');
-		var lightPhysicalFragmentChunk = ['PhysicalMaterial material;', 'material.diffuseColor = diffuseColor.rgb * ( 1. - max( specularFactor.r, max( specularFactor.g, specularFactor.b ) ) );', 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );', 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );', 'material.specularRoughness = max( 1.0 - glossinessFactor, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.', 'material.specularRoughness += geometryRoughness;', 'material.specularRoughness = min( material.specularRoughness, 1.0 );', 'material.specularColor = specularFactor;'].join('\n');
-		var uniforms = {
-			specular: {
-				value: new THREE.Color().setHex(0xffffff)
-			},
-			glossiness: {
-				value: 1
-			},
-			specularMap: {
-				value: null
-			},
-			glossinessMap: {
-				value: null
-			}
-		};
-		this._extraUniforms = uniforms;
+				} );
 
-		this.onBeforeCompile = function (shader) {
-			for (var uniformName in uniforms) {
-				shader.uniforms[uniformName] = uniforms[uniformName];
 			}
 
-			shader.fragmentShader = shader.fragmentShader.replace('uniform float roughness;', 'uniform vec3 specular;').replace('uniform float metalness;', 'uniform float glossiness;').replace('#include <roughnessmap_pars_fragment>', specularMapParsFragmentChunk).replace('#include <metalnessmap_pars_fragment>', glossinessMapParsFragmentChunk).replace('#include <roughnessmap_fragment>', specularMapFragmentChunk).replace('#include <metalnessmap_fragment>', glossinessMapFragmentChunk).replace('#include <lights_physical_fragment>', lightPhysicalFragmentChunk);
-		};
+			for ( var gltfAttributeName in attributes ) {
 
-		Object.defineProperties(this, {
-			specular: {
-				get: function () {
-					return uniforms.specular.value;
-				},
-				set: function (v) {
-					uniforms.specular.value = v;
-				}
-			},
-			specularMap: {
-				get: function () {
-					return uniforms.specularMap.value;
-				},
-				set: function (v) {
-					uniforms.specularMap.value = v;
+				var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); // Skip attributes already provided by e.g. Draco extension.
 
-					if (v) {
-						this.defines.USE_SPECULARMAP = ''; // USE_UV is set by the renderer for specular maps
-					} else {
-						delete this.defines.USE_SPECULARMAP;
-					}
-				}
-			},
-			glossiness: {
-				get: function () {
-					return uniforms.glossiness.value;
-				},
-				set: function (v) {
-					uniforms.glossiness.value = v;
-				}
-			},
-			glossinessMap: {
-				get: function () {
-					return uniforms.glossinessMap.value;
-				},
-				set: function (v) {
-					uniforms.glossinessMap.value = v;
+				if ( threeAttributeName in geometry.attributes ) continue;
+				pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );
 
-					if (v) {
-						this.defines.USE_GLOSSINESSMAP = '';
-						this.defines.USE_UV = '';
-					} else {
-						delete this.defines.USE_GLOSSINESSMAP;
-						delete this.defines.USE_UV;
-					}
-				}
 			}
-		});
-		delete this.metalness;
-		delete this.roughness;
-		delete this.metalnessMap;
-		delete this.roughnessMap;
-		this.setValues(params);
-	}
-
-	GLTFMeshStandardSGMaterial.prototype = Object.create(THREE.MeshStandardMaterial.prototype);
-	GLTFMeshStandardSGMaterial.prototype.constructor = GLTFMeshStandardSGMaterial;
-
-	GLTFMeshStandardSGMaterial.prototype.copy = function (source) {
-		THREE.MeshStandardMaterial.prototype.copy.call(this, source);
-		this.specularMap = source.specularMap;
-		this.specular.copy(source.specular);
-		this.glossinessMap = source.glossinessMap;
-		this.glossiness = source.glossiness;
-		delete this.metalness;
-		delete this.roughness;
-		delete this.metalnessMap;
-		delete this.roughnessMap;
-		return this;
-	};
-
-	function GLTFMaterialsPbrSpecularGlossinessExtension() {
-		return {
-			name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,
-			specularGlossinessParams: ['color', 'map', 'lightMap', 'lightMapIntensity', 'aoMap', 'aoMapIntensity', 'emissive', 'emissiveIntensity', 'emissiveMap', 'bumpMap', 'bumpScale', 'normalMap', 'normalMapType', 'displacementMap', 'displacementScale', 'displacementBias', 'specularMap', 'specular', 'glossinessMap', 'glossiness', 'alphaMap', 'envMap', 'envMapIntensity', 'refractionRatio'],
-			getMaterialType: function () {
-				return GLTFMeshStandardSGMaterial;
-			},
-			extendParams: function (materialParams, materialDef, parser) {
-				var pbrSpecularGlossiness = materialDef.extensions[this.name];
-				materialParams.color = new THREE.Color(1.0, 1.0, 1.0);
-				materialParams.opacity = 1.0;
-				var pending = [];
-
-				if (Array.isArray(pbrSpecularGlossiness.diffuseFactor)) {
-					var array = pbrSpecularGlossiness.diffuseFactor;
-					materialParams.color.fromArray(array);
-					materialParams.opacity = array[3];
-				}
 
-				if (pbrSpecularGlossiness.diffuseTexture !== undefined) {
-					pending.push(parser.assignTexture(materialParams, 'map', pbrSpecularGlossiness.diffuseTexture));
-				}
+			if ( primitiveDef.indices !== undefined && ! geometry.index ) {
 
-				materialParams.emissive = new THREE.Color(0.0, 0.0, 0.0);
-				materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
-				materialParams.specular = new THREE.Color(1.0, 1.0, 1.0);
+				var accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {
 
-				if (Array.isArray(pbrSpecularGlossiness.specularFactor)) {
-					materialParams.specular.fromArray(pbrSpecularGlossiness.specularFactor);
-				}
+					geometry.setIndex( accessor );
 
-				if (pbrSpecularGlossiness.specularGlossinessTexture !== undefined) {
-					var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture;
-					pending.push(parser.assignTexture(materialParams, 'glossinessMap', specGlossMapDef));
-					pending.push(parser.assignTexture(materialParams, 'specularMap', specGlossMapDef));
-				}
+				} );
+				pending.push( accessor );
 
-				return Promise.all(pending);
-			},
-			createMaterial: function (materialParams) {
-				var material = new GLTFMeshStandardSGMaterial(materialParams);
-				material.fog = true;
-				material.color = materialParams.color;
-				material.map = materialParams.map === undefined ? null : materialParams.map;
-				material.lightMap = null;
-				material.lightMapIntensity = 1.0;
-				material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap;
-				material.aoMapIntensity = 1.0;
-				material.emissive = materialParams.emissive;
-				material.emissiveIntensity = 1.0;
-				material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap;
-				material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap;
-				material.bumpScale = 1;
-				material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap;
-				material.normalMapType = THREE.TangentSpaceNormalMap;
-				if (materialParams.normalScale) material.normalScale = materialParams.normalScale;
-				material.displacementMap = null;
-				material.displacementScale = 1;
-				material.displacementBias = 0;
-				material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap;
-				material.specular = materialParams.specular;
-				material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap;
-				material.glossiness = materialParams.glossiness;
-				material.alphaMap = null;
-				material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap;
-				material.envMapIntensity = 1.0;
-				material.refractionRatio = 0.98;
-				return material;
 			}
-		};
-	}
-	/**
-	 * THREE.Mesh Quantization Extension
-	 *
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization
-	 */
 
+			assignExtrasToUserData( geometry, primitiveDef );
+			computeBounds( geometry, primitiveDef, parser );
+			return Promise.all( pending ).then( function () {
 
-	function GLTFMeshQuantizationExtension() {
-		this.name = EXTENSIONS.KHR_MESH_QUANTIZATION;
-	}
-	/*********************************/
+				return primitiveDef.targets !== undefined ? addMorphTargets( geometry, primitiveDef.targets, parser ) : geometry;
 
-	/********** INTERPOLATION ********/
+			} );
+
+		}
+		/**
+	 * @param {BufferGeometry} geometry
+	 * @param {Number} drawMode
+	 * @return {BufferGeometry}
+	 */
 
-	/*********************************/
-	// Spline Interpolation
-	// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
 
+		function toTrianglesDrawMode( geometry, drawMode ) {
 
-	function GLTFCubicSplineInterpolant(parameterPositions, sampleValues, sampleSize, resultBuffer) {
-		THREE.Interpolant.call(this, parameterPositions, sampleValues, sampleSize, resultBuffer);
-	}
+			var index = geometry.getIndex(); // generate index if not present
 
-	GLTFCubicSplineInterpolant.prototype = Object.create(THREE.Interpolant.prototype);
-	GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant;
+			if ( index === null ) {
 
-	GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function (index) {
-		// Copies a sample value to the result buffer. See description of glTF
-		// CUBICSPLINE values layout in interpolate_() function below.
-		var result = this.resultBuffer,
-				values = this.sampleValues,
-				valueSize = this.valueSize,
-				offset = index * valueSize * 3 + valueSize;
+				var indices = [];
+				var position = geometry.getAttribute( 'position' );
 
-		for (var i = 0; i !== valueSize; i++) {
-			result[i] = values[offset + i];
-		}
+				if ( position !== undefined ) {
 
-		return result;
-	};
+					for ( var i = 0; i < position.count; i ++ ) {
 
-	GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
-	GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_;
+						indices.push( i );
 
-	GLTFCubicSplineInterpolant.prototype.interpolate_ = function (i1, t0, t, t1) {
-		var result = this.resultBuffer;
-		var values = this.sampleValues;
-		var stride = this.valueSize;
-		var stride2 = stride * 2;
-		var stride3 = stride * 3;
-		var td = t1 - t0;
-		var p = (t - t0) / td;
-		var pp = p * p;
-		var ppp = pp * p;
-		var offset1 = i1 * stride3;
-		var offset0 = offset1 - stride3;
-		var s2 = -2 * ppp + 3 * pp;
-		var s3 = ppp - pp;
-		var s0 = 1 - s2;
-		var s1 = s3 - pp + p; // Layout of keyframe output values for CUBICSPLINE animations:
-		//	 [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
+					}
 
-		for (var i = 0; i !== stride; i++) {
-			var p0 = values[offset0 + i + stride]; // splineVertex_k
+					geometry.setIndex( indices );
+					index = geometry.getIndex();
 
-			var m0 = values[offset0 + i + stride2] * td; // outTangent_k * (t_k+1 - t_k)
+				} else {
 
-			var p1 = values[offset1 + i + stride]; // splineVertex_k+1
+					console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
+					return geometry;
 
-			var m1 = values[offset1 + i] * td; // inTangent_k+1 * (t_k+1 - t_k)
+				}
 
-			result[i] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
-		}
+			} //
 
-		return result;
-	};
-	/*********************************/
-
-	/********** INTERNALS ************/
-
-	/*********************************/
-
-	/* CONSTANTS */
-
-
-	var WEBGL_CONSTANTS = {
-		FLOAT: 5126,
-		//FLOAT_MAT2: 35674,
-		FLOAT_MAT3: 35675,
-		FLOAT_MAT4: 35676,
-		FLOAT_VEC2: 35664,
-		FLOAT_VEC3: 35665,
-		FLOAT_VEC4: 35666,
-		LINEAR: 9729,
-		REPEAT: 10497,
-		SAMPLER_2D: 35678,
-		POINTS: 0,
-		LINES: 1,
-		LINE_LOOP: 2,
-		LINE_STRIP: 3,
-		TRIANGLES: 4,
-		TRIANGLE_STRIP: 5,
-		TRIANGLE_FAN: 6,
-		UNSIGNED_BYTE: 5121,
-		UNSIGNED_SHORT: 5123
-	};
-	var WEBGL_COMPONENT_TYPES = {
-		5120: Int8Array,
-		5121: Uint8Array,
-		5122: Int16Array,
-		5123: Uint16Array,
-		5125: Uint32Array,
-		5126: Float32Array
-	};
-	var WEBGL_FILTERS = {
-		9728: THREE.NearestFilter,
-		9729: THREE.LinearFilter,
-		9984: THREE.NearestMipmapNearestFilter,
-		9985: THREE.LinearMipmapNearestFilter,
-		9986: THREE.NearestMipmapLinearFilter,
-		9987: THREE.LinearMipmapLinearFilter
-	};
-	var WEBGL_WRAPPINGS = {
-		33071: THREE.ClampToEdgeWrapping,
-		33648: THREE.MirroredRepeatWrapping,
-		10497: THREE.RepeatWrapping
-	};
-	var WEBGL_TYPE_SIZES = {
-		'SCALAR': 1,
-		'VEC2': 2,
-		'VEC3': 3,
-		'VEC4': 4,
-		'MAT2': 4,
-		'MAT3': 9,
-		'MAT4': 16
-	};
-	var ATTRIBUTES = {
-		POSITION: 'position',
-		NORMAL: 'normal',
-		TANGENT: 'tangent',
-		TEXCOORD_0: 'uv',
-		TEXCOORD_1: 'uv2',
-		COLOR_0: 'color',
-		WEIGHTS_0: 'skinWeight',
-		JOINTS_0: 'skinIndex'
-	};
-	var PATH_PROPERTIES = {
-		scale: 'scale',
-		translation: 'position',
-		rotation: 'quaternion',
-		weights: 'morphTargetInfluences'
-	};
-	var INTERPOLATION = {
-		CUBICSPLINE: undefined,
-		// We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
-		// keyframe track will be initialized with a default interpolation type, then modified.
-		LINEAR: THREE.InterpolateLinear,
-		STEP: THREE.InterpolateDiscrete
-	};
-	var ALPHA_MODES = {
-		OPAQUE: 'OPAQUE',
-		MASK: 'MASK',
-		BLEND: 'BLEND'
-	};
-	/* UTILITY FUNCTIONS */
-
-	function resolveURL(url, path) {
-		// Invalid URL
-		if (typeof url !== 'string' || url === '') return ''; // Host Relative URL
-
-		if (/^https?:\/\//i.test(path) && /^\//.test(url)) {
-			path = path.replace(/(^https?:\/\/[^\/]+).*/i, '$1');
-		} // Absolute URL http://,https://,//
-
-
-		if (/^(https?:)?\/\//i.test(url)) return url; // Data URI
-
-		if (/^data:.*,.*$/i.test(url)) return url; // Blob URL
-
-		if (/^blob:.*$/i.test(url)) return url; // Relative URL
-
-		return path + url;
-	}
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
-	 */
 
+			var numberOfTriangles = index.count - 2;
+			var newIndices = [];
 
-	function createDefaultMaterial(cache) {
-		if (cache['DefaultMaterial'] === undefined) {
-			cache['DefaultMaterial'] = new THREE.MeshStandardMaterial({
-				color: 0xFFFFFF,
-				emissive: 0x000000,
-				metalness: 1,
-				roughness: 1,
-				transparent: false,
-				depthTest: true,
-				side: THREE.FrontSide
-			});
-		}
+			if ( drawMode === THREE.TriangleFanDrawMode ) {
 
-		return cache['DefaultMaterial'];
-	}
+				// gl.TRIANGLE_FAN
+				for ( var i = 1; i <= numberOfTriangles; i ++ ) {
 
-	function addUnknownExtensionsToUserData(knownExtensions, object, objectDef) {
-		// Add unknown glTF extensions to an object's userData.
-		for (var name in objectDef.extensions) {
-			if (knownExtensions[name] === undefined) {
-				object.userData.gltfExtensions = object.userData.gltfExtensions || {};
-				object.userData.gltfExtensions[name] = objectDef.extensions[name];
-			}
-		}
-	}
-	/**
-	 * @param {Object3D|Material|BufferGeometry} object
-	 * @param {GLTF.definition} gltfDef
-	 */
+					newIndices.push( index.getX( 0 ) );
+					newIndices.push( index.getX( i ) );
+					newIndices.push( index.getX( i + 1 ) );
 
+				}
 
-	function assignExtrasToUserData(object, gltfDef) {
-		if (gltfDef.extras !== undefined) {
-			if (typeof gltfDef.extras === 'object') {
-				Object.assign(object.userData, gltfDef.extras);
 			} else {
-				console.warn('THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras);
-			}
-		}
-	}
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
-	 *
-	 * @param {BufferGeometry} geometry
-	 * @param {Array<GLTF.Target>} targets
-	 * @param {GLTFParser} parser
-	 * @return {Promise<BufferGeometry>}
-	 */
 
+				// gl.TRIANGLE_STRIP
+				for ( var i = 0; i < numberOfTriangles; i ++ ) {
 
-	function addMorphTargets(geometry, targets, parser) {
-		var hasMorphPosition = false;
-		var hasMorphNormal = false;
+					if ( i % 2 === 0 ) {
 
-		for (var i = 0, il = targets.length; i < il; i++) {
-			var target = targets[i];
-			if (target.POSITION !== undefined) hasMorphPosition = true;
-			if (target.NORMAL !== undefined) hasMorphNormal = true;
-			if (hasMorphPosition && hasMorphNormal) break;
-		}
+						newIndices.push( index.getX( i ) );
+						newIndices.push( index.getX( i + 1 ) );
+						newIndices.push( index.getX( i + 2 ) );
+
+					} else {
 
-		if (!hasMorphPosition && !hasMorphNormal) return Promise.resolve(geometry);
-		var pendingPositionAccessors = [];
-		var pendingNormalAccessors = [];
+						newIndices.push( index.getX( i + 2 ) );
+						newIndices.push( index.getX( i + 1 ) );
+						newIndices.push( index.getX( i ) );
 
-		for (var i = 0, il = targets.length; i < il; i++) {
-			var target = targets[i];
+					}
 
-			if (hasMorphPosition) {
-				var pendingAccessor = target.POSITION !== undefined ? parser.getDependency('accessor', target.POSITION) : geometry.attributes.position;
-				pendingPositionAccessors.push(pendingAccessor);
-			}
+				}
 
-			if (hasMorphNormal) {
-				var pendingAccessor = target.NORMAL !== undefined ? parser.getDependency('accessor', target.NORMAL) : geometry.attributes.normal;
-				pendingNormalAccessors.push(pendingAccessor);
 			}
-		}
 
-		return Promise.all([Promise.all(pendingPositionAccessors), Promise.all(pendingNormalAccessors)]).then(function (accessors) {
-			var morphPositions = accessors[0];
-			var morphNormals = accessors[1];
-			if (hasMorphPosition) geometry.morphAttributes.position = morphPositions;
-			if (hasMorphNormal) geometry.morphAttributes.normal = morphNormals;
-			geometry.morphTargetsRelative = true;
-			return geometry;
-		});
-	}
-	/**
-	 * @param {Mesh} mesh
-	 * @param {GLTF.Mesh} meshDef
+			if ( newIndices.length / 3 !== numberOfTriangles ) {
+
+				console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );
+
+			} // build final geometry
+
+
+			var newGeometry = geometry.clone();
+			newGeometry.setIndex( newIndices );
+			return newGeometry;
+
+		}
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
+	 *
+	 * Creates BufferGeometries from primitives.
+	 *
+	 * @param {Array<GLTF.Primitive>} primitives
+	 * @return {Promise<Array<BufferGeometry>>}
 	 */
 
 
-	function updateMorphTargets(mesh, meshDef) {
-		mesh.updateMorphTargets();
+		GLTFParser.prototype.loadGeometries = function ( primitives ) {
 
-		if (meshDef.weights !== undefined) {
-			for (var i = 0, il = meshDef.weights.length; i < il; i++) {
-				mesh.morphTargetInfluences[i] = meshDef.weights[i];
-			}
-		} // .extras has user-defined data, so check that .extras.targetNames is an array.
+			var parser = this;
+			var extensions = this.extensions;
+			var cache = this.primitiveCache;
 
+			function createDracoPrimitive( primitive ) {
 
-		if (meshDef.extras && Array.isArray(meshDef.extras.targetNames)) {
-			var targetNames = meshDef.extras.targetNames;
+				return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ].decodePrimitive( primitive, parser ).then( function ( geometry ) {
 
-			if (mesh.morphTargetInfluences.length === targetNames.length) {
-				mesh.morphTargetDictionary = {};
+					return addPrimitiveAttributes( geometry, primitive, parser );
+
+				} );
 
-				for (var i = 0, il = targetNames.length; i < il; i++) {
-					mesh.morphTargetDictionary[targetNames[i]] = i;
-				}
-			} else {
-				console.warn('THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.');
 			}
-		}
-	}
 
-	function createPrimitiveKey(primitiveDef) {
-		var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION];
-		var geometryKey;
+			var pending = [];
 
-		if (dracoExtension) {
-			geometryKey = 'draco:' + dracoExtension.bufferView + ':' + dracoExtension.indices + ':' + createAttributesKey(dracoExtension.attributes);
-		} else {
-			geometryKey = primitiveDef.indices + ':' + createAttributesKey(primitiveDef.attributes) + ':' + primitiveDef.mode;
-		}
+			for ( var i = 0, il = primitives.length; i < il; i ++ ) {
 
-		return geometryKey;
-	}
+				var primitive = primitives[ i ];
+				var cacheKey = createPrimitiveKey( primitive ); // See if we've already created this geometry
 
-	function createAttributesKey(attributes) {
-		var attributesKey = '';
-		var keys = Object.keys(attributes).sort();
+				var cached = cache[ cacheKey ];
 
-		for (var i = 0, il = keys.length; i < il; i++) {
-			attributesKey += keys[i] + ':' + attributes[keys[i]] + ';';
-		}
+				if ( cached ) {
 
-		return attributesKey;
-	}
+					// Use the cached geometry if it exists
+					pending.push( cached.promise );
 
-	function getNormalizedComponentScale(constructor) {
-		// Reference:
-		// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data
-		switch (constructor) {
-			case Int8Array:
-				return 1 / 127;
+				} else {
 
-			case Uint8Array:
-				return 1 / 255;
+					var geometryPromise;
 
-			case Int16Array:
-				return 1 / 32767;
+					if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) {
 
-			case Uint16Array:
-				return 1 / 65535;
+						// Use DRACO geometry if available
+						geometryPromise = createDracoPrimitive( primitive );
 
-			default:
-				throw new Error('THREE.GLTFLoader: Unsupported normalized accessor component type.');
-		}
-	}
-	/* GLTF PARSER */
+					} else {
 
+						// Otherwise create a new geometry
+						geometryPromise = addPrimitiveAttributes( new THREE.BufferGeometry(), primitive, parser );
 
-	function GLTFParser(json, options) {
-		this.json = json || {};
-		this.extensions = {};
-		this.plugins = {};
-		this.options = options || {}; // loader object cache
+					} // Cache this geometry
 
-		this.cache = new GLTFRegistry(); // associations between Three.js objects and glTF elements
 
-		this.associations = new Map(); // THREE.BufferGeometry caching
+					cache[ cacheKey ] = {
+						primitive: primitive,
+						promise: geometryPromise
+					};
+					pending.push( geometryPromise );
 
-		this.primitiveCache = {}; // THREE.Object3D instance caches
+				}
 
-		this.meshCache = {
-			refs: {},
-			uses: {}
-		};
-		this.cameraCache = {
-			refs: {},
-			uses: {}
-		};
-		this.lightCache = {
-			refs: {},
-			uses: {}
-		}; // Track node names, to ensure no duplicates
-
-		this.nodeNamesUsed = {}; // Use an THREE.ImageBitmapLoader if imageBitmaps are supported. Moves much of the
-		// expensive work of uploading a texture to the GPU off the main thread.
-
-		if (typeof createImageBitmap !== 'undefined' && /Firefox/.test(navigator.userAgent) === false) {
-			this.textureLoader = new THREE.ImageBitmapLoader(this.options.manager);
-		} else {
-			this.textureLoader = new THREE.TextureLoader(this.options.manager);
-		}
+			}
 
-		this.textureLoader.setCrossOrigin(this.options.crossOrigin);
-		this.textureLoader.setRequestHeader(this.options.requestHeader);
-		this.fileLoader = new THREE.FileLoader(this.options.manager);
-		this.fileLoader.setResponseType('arraybuffer');
+			return Promise.all( pending );
 
-		if (this.options.crossOrigin === 'use-credentials') {
-			this.fileLoader.setWithCredentials(true);
-		}
-	}
-
-	GLTFParser.prototype.setExtensions = function (extensions) {
-		this.extensions = extensions;
-	};
-
-	GLTFParser.prototype.setPlugins = function (plugins) {
-		this.plugins = plugins;
-	};
-
-	GLTFParser.prototype.parse = function (onLoad, onError) {
-		var parser = this;
-		var json = this.json;
-		var extensions = this.extensions; // Clear the loader cache
-
-		this.cache.removeAll(); // Mark the special nodes/meshes in json for efficient parse
-
-		this._invokeAll(function (ext) {
-			return ext._markDefs && ext._markDefs();
-		});
-
-		Promise.all(this._invokeAll(function (ext) {
-			return ext.beforeRoot && ext.beforeRoot();
-		})).then(function () {
-			return Promise.all([parser.getDependencies('scene'), parser.getDependencies('animation'), parser.getDependencies('camera')]);
-		}).then(function (dependencies) {
-			var result = {
-				scene: dependencies[0][json.scene || 0],
-				scenes: dependencies[0],
-				animations: dependencies[1],
-				cameras: dependencies[2],
-				asset: json.asset,
-				parser: parser,
-				userData: {}
-			};
-			addUnknownExtensionsToUserData(extensions, result, json);
-			assignExtrasToUserData(result, json);
-			Promise.all(parser._invokeAll(function (ext) {
-				return ext.afterRoot && ext.afterRoot(result);
-			})).then(function () {
-				onLoad(result);
-			});
-		}).catch(onError);
-	};
-	/**
-	 * Marks the special nodes/meshes in json for efficient parse.
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
+	 * @param {number} meshIndex
+	 * @return {Promise<Group|Mesh|SkinnedMesh>}
 	 */
 
 
-	GLTFParser.prototype._markDefs = function () {
-		var nodeDefs = this.json.nodes || [];
-		var skinDefs = this.json.skins || [];
-		var meshDefs = this.json.meshes || []; // Nothing in the node definition indicates whether it is a THREE.Bone or an
-		// THREE.Object3D. Use the skins' joint references to mark bones.
+		GLTFParser.prototype.loadMesh = function ( meshIndex ) {
+
+			var parser = this;
+			var json = this.json;
+			var extensions = this.extensions;
+			var meshDef = json.meshes[ meshIndex ];
+			var primitives = meshDef.primitives;
+			var pending = [];
+
+			for ( var i = 0, il = primitives.length; i < il; i ++ ) {
 
-		for (var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex++) {
-			var joints = skinDefs[skinIndex].joints;
+				var material = primitives[ i ].material === undefined ? createDefaultMaterial( this.cache ) : this.getDependency( 'material', primitives[ i ].material );
+				pending.push( material );
 
-			for (var i = 0, il = joints.length; i < il; i++) {
-				nodeDefs[joints[i]].isBone = true;
 			}
-		} // Iterate over all nodes, marking references to shared resources,
-		// as well as skeleton joints.
 
+			pending.push( parser.loadGeometries( primitives ) );
+			return Promise.all( pending ).then( function ( results ) {
 
-		for (var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex++) {
-			var nodeDef = nodeDefs[nodeIndex];
+				var materials = results.slice( 0, results.length - 1 );
+				var geometries = results[ results.length - 1 ];
+				var meshes = [];
 
-			if (nodeDef.mesh !== undefined) {
-				this._addNodeRef(this.meshCache, nodeDef.mesh); // Nothing in the mesh definition indicates whether it is
-				// a THREE.SkinnedMesh or THREE.Mesh. Use the node's mesh reference
-				// to mark THREE.SkinnedMesh if node has skin.
+				for ( var i = 0, il = geometries.length; i < il; i ++ ) {
 
+					var geometry = geometries[ i ];
+					var primitive = primitives[ i ]; // 1. create THREE.Mesh
 
-				if (nodeDef.skin !== undefined) {
-					meshDefs[nodeDef.mesh].isSkinnedMesh = true;
-				}
-			}
+					var mesh;
+					var material = materials[ i ];
 
-			if (nodeDef.camera !== undefined) {
-				this._addNodeRef(this.cameraCache, nodeDef.camera);
-			}
-		}
-	};
-	/**
-	 * Counts references to shared node / THREE.Object3D resources. These resources
-	 * can be reused, or "instantiated", at multiple nodes in the scene
-	 * hierarchy. THREE.Mesh, Camera, and Light instances are instantiated and must
-	 * be marked. Non-scenegraph resources (like Materials, Geometries, and
-	 * Textures) can be reused directly and are not marked here.
-	 *
-	 * Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
-	 */
+					if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || primitive.mode === undefined ) {
 
+						// .isSkinnedMesh isn't in glTF spec. See ._markDefs()
+						mesh = meshDef.isSkinnedMesh === true ? new THREE.SkinnedMesh( geometry, material ) : new THREE.Mesh( geometry, material );
 
-	GLTFParser.prototype._addNodeRef = function (cache, index) {
-		if (index === undefined) return;
+						if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {
 
-		if (cache.refs[index] === undefined) {
-			cache.refs[index] = cache.uses[index] = 0;
-		}
+							// we normalize floating point skin weight array to fix malformed assets (see #15319)
+							// it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
+							mesh.normalizeSkinWeights();
 
-		cache.refs[index]++;
-	};
-	/** Returns a reference to a shared resource, cloning it if necessary. */
+						}
 
+						if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {
 
-	GLTFParser.prototype._getNodeRef = function (cache, index, object) {
-		if (cache.refs[index] <= 1) return object;
-		var ref = object.clone();
-		ref.name += '_instance_' + cache.uses[index]++;
-		return ref;
-	};
+							mesh.geometry = toTrianglesDrawMode( mesh.geometry, THREE.TriangleStripDrawMode );
 
-	GLTFParser.prototype._invokeOne = function (func) {
-		var extensions = Object.values(this.plugins);
-		extensions.push(this);
+						} else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {
 
-		for (var i = 0; i < extensions.length; i++) {
-			var result = func(extensions[i]);
-			if (result) return result;
-		}
+							mesh.geometry = toTrianglesDrawMode( mesh.geometry, THREE.TriangleFanDrawMode );
 
-		return null;
-	};
+						}
 
-	GLTFParser.prototype._invokeAll = function (func) {
-		var extensions = Object.values(this.plugins);
-		extensions.unshift(this);
-		var pending = [];
+					} else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) {
 
-		for (var i = 0; i < extensions.length; i++) {
-			var result = func(extensions[i]);
-			if (result) pending.push(result);
-		}
+						mesh = new THREE.LineSegments( geometry, material );
 
-		return pending;
-	};
-	/**
-	 * Requests the specified dependency asynchronously, with caching.
-	 * @param {string} type
-	 * @param {number} index
-	 * @return {Promise<Object3D|Material|THREE.Texture|AnimationClip|ArrayBuffer|Object>}
-	 */
+					} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) {
 
+						mesh = new THREE.Line( geometry, material );
 
-	GLTFParser.prototype.getDependency = function (type, index) {
-		var cacheKey = type + ':' + index;
-		var dependency = this.cache.get(cacheKey);
+					} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) {
 
-		if (!dependency) {
-			switch (type) {
-				case 'scene':
-					dependency = this.loadScene(index);
-					break;
+						mesh = new THREE.LineLoop( geometry, material );
 
-				case 'node':
-					dependency = this.loadNode(index);
-					break;
+					} else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) {
 
-				case 'mesh':
-					dependency = this._invokeOne(function (ext) {
-						return ext.loadMesh && ext.loadMesh(index);
-					});
-					break;
+						mesh = new THREE.Points( geometry, material );
 
-				case 'accessor':
-					dependency = this.loadAccessor(index);
-					break;
+					} else {
 
-				case 'bufferView':
-					dependency = this._invokeOne(function (ext) {
-						return ext.loadBufferView && ext.loadBufferView(index);
-					});
-					break;
+						throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );
 
-				case 'buffer':
-					dependency = this.loadBuffer(index);
-					break;
+					}
 
-				case 'material':
-					dependency = this._invokeOne(function (ext) {
-						return ext.loadMaterial && ext.loadMaterial(index);
-					});
-					break;
+					if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {
 
-				case 'texture':
-					dependency = this._invokeOne(function (ext) {
-						return ext.loadTexture && ext.loadTexture(index);
-					});
-					break;
+						updateMorphTargets( mesh, meshDef );
 
-				case 'skin':
-					dependency = this.loadSkin(index);
-					break;
+					}
 
-				case 'animation':
-					dependency = this.loadAnimation(index);
-					break;
+					mesh.name = parser.createUniqueName( meshDef.name || 'mesh_' + meshIndex );
+					assignExtrasToUserData( mesh, meshDef );
+					if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive );
+					parser.assignFinalMaterial( mesh );
+					meshes.push( mesh );
 
-				case 'camera':
-					dependency = this.loadCamera(index);
-					break;
+				}
 
-				default:
-					throw new Error('Unknown type: ' + type);
-			}
+				if ( meshes.length === 1 ) {
 
-			this.cache.add(cacheKey, dependency);
-		}
+					return meshes[ 0 ];
 
-		return dependency;
-	};
-	/**
-	 * Requests all dependencies of the specified type asynchronously, with caching.
-	 * @param {string} type
-	 * @return {Promise<Array<Object>>}
-	 */
+				}
 
+				var group = new THREE.Group();
 
-	GLTFParser.prototype.getDependencies = function (type) {
-		var dependencies = this.cache.get(type);
+				for ( var i = 0, il = meshes.length; i < il; i ++ ) {
 
-		if (!dependencies) {
-			var parser = this;
-			var defs = this.json[type + (type === 'mesh' ? 'es' : 's')] || [];
-			dependencies = Promise.all(defs.map(function (def, index) {
-				return parser.getDependency(type, index);
-			}));
-			this.cache.add(type, dependencies);
-		}
+					group.add( meshes[ i ] );
 
-		return dependencies;
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
-	 * @param {number} bufferIndex
-	 * @return {Promise<ArrayBuffer>}
-	 */
+				}
+
+				return group;
 
+			} );
 
-	GLTFParser.prototype.loadBuffer = function (bufferIndex) {
-		var bufferDef = this.json.buffers[bufferIndex];
-		var loader = this.fileLoader;
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
+	 * @param {number} cameraIndex
+	 * @return {Promise<THREE.Camera>}
+	 */
 
-		if (bufferDef.type && bufferDef.type !== 'arraybuffer') {
-			throw new Error('THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.');
-		} // If present, GLB container is required to be the first buffer.
 
+		GLTFParser.prototype.loadCamera = function ( cameraIndex ) {
 
-		if (bufferDef.uri === undefined && bufferIndex === 0) {
-			return Promise.resolve(this.extensions[EXTENSIONS.KHR_BINARY_GLTF].body);
-		}
+			var camera;
+			var cameraDef = this.json.cameras[ cameraIndex ];
+			var params = cameraDef[ cameraDef.type ];
 
-		var options = this.options;
-		return new Promise(function (resolve, reject) {
-			loader.load(resolveURL(bufferDef.uri, options.path), resolve, undefined, function () {
-				reject(new Error('THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".'));
-			});
-		});
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
-	 * @param {number} bufferViewIndex
-	 * @return {Promise<ArrayBuffer>}
-	 */
+			if ( ! params ) {
 
+				console.warn( 'THREE.GLTFLoader: Missing camera parameters.' );
+				return;
 
-	GLTFParser.prototype.loadBufferView = function (bufferViewIndex) {
-		var bufferViewDef = this.json.bufferViews[bufferViewIndex];
-		return this.getDependency('buffer', bufferViewDef.buffer).then(function (buffer) {
-			var byteLength = bufferViewDef.byteLength || 0;
-			var byteOffset = bufferViewDef.byteOffset || 0;
-			return buffer.slice(byteOffset, byteOffset + byteLength);
-		});
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
-	 * @param {number} accessorIndex
-	 * @return {Promise<BufferAttribute|InterleavedBufferAttribute>}
-	 */
+			}
 
+			if ( cameraDef.type === 'perspective' ) {
 
-	GLTFParser.prototype.loadAccessor = function (accessorIndex) {
-		var parser = this;
-		var json = this.json;
-		var accessorDef = this.json.accessors[accessorIndex];
+				camera = new THREE.PerspectiveCamera( THREE.MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 );
 
-		if (accessorDef.bufferView === undefined && accessorDef.sparse === undefined) {
-			// Ignore empty accessors, which may be used to declare runtime
-			// information about attributes coming from another source (e.g. Draco
-			// compression extension).
-			return Promise.resolve(null);
-		}
+			} else if ( cameraDef.type === 'orthographic' ) {
 
-		var pendingBufferViews = [];
+				camera = new THREE.OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar );
 
-		if (accessorDef.bufferView !== undefined) {
-			pendingBufferViews.push(this.getDependency('bufferView', accessorDef.bufferView));
-		} else {
-			pendingBufferViews.push(null);
-		}
+			}
 
-		if (accessorDef.sparse !== undefined) {
-			pendingBufferViews.push(this.getDependency('bufferView', accessorDef.sparse.indices.bufferView));
-			pendingBufferViews.push(this.getDependency('bufferView', accessorDef.sparse.values.bufferView));
-		}
+			if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name );
+			assignExtrasToUserData( camera, cameraDef );
+			return Promise.resolve( camera );
 
-		return Promise.all(pendingBufferViews).then(function (bufferViews) {
-			var bufferView = bufferViews[0];
-			var itemSize = WEBGL_TYPE_SIZES[accessorDef.type];
-			var TypedArray = WEBGL_COMPONENT_TYPES[accessorDef.componentType]; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
-
-			var elementBytes = TypedArray.BYTES_PER_ELEMENT;
-			var itemBytes = elementBytes * itemSize;
-			var byteOffset = accessorDef.byteOffset || 0;
-			var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[accessorDef.bufferView].byteStride : undefined;
-			var normalized = accessorDef.normalized === true;
-			var array, bufferAttribute; // The buffer is not interleaved if the stride is the item size in bytes.
-
-			if (byteStride && byteStride !== itemBytes) {
-				// Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own THREE.InterleavedBuffer
-				// This makes sure that IBA.count reflects accessor.count properly
-				var ibSlice = Math.floor(byteOffset / byteStride);
-				var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count;
-				var ib = parser.cache.get(ibCacheKey);
-
-				if (!ib) {
-					array = new TypedArray(bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes); // Integer parameters to IB/IBA are in array elements, not bytes.
-
-					ib = new THREE.InterleavedBuffer(array, byteStride / elementBytes);
-					parser.cache.add(ibCacheKey, ib);
-				}
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
+	 * @param {number} skinIndex
+	 * @return {Promise<Object>}
+	 */
 
-				bufferAttribute = new THREE.InterleavedBufferAttribute(ib, itemSize, byteOffset % byteStride / elementBytes, normalized);
-			} else {
-				if (bufferView === null) {
-					array = new TypedArray(accessorDef.count * itemSize);
-				} else {
-					array = new TypedArray(bufferView, byteOffset, accessorDef.count * itemSize);
-				}
 
-				bufferAttribute = new THREE.BufferAttribute(array, itemSize, normalized);
-			} // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
+		GLTFParser.prototype.loadSkin = function ( skinIndex ) {
 
+			var skinDef = this.json.skins[ skinIndex ];
+			var skinEntry = {
+				joints: skinDef.joints
+			};
 
-			if (accessorDef.sparse !== undefined) {
-				var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
-				var TypedArrayIndices = WEBGL_COMPONENT_TYPES[accessorDef.sparse.indices.componentType];
-				var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
-				var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;
-				var sparseIndices = new TypedArrayIndices(bufferViews[1], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices);
-				var sparseValues = new TypedArray(bufferViews[2], byteOffsetValues, accessorDef.sparse.count * itemSize);
+			if ( skinDef.inverseBindMatrices === undefined ) {
 
-				if (bufferView !== null) {
-					// Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
-					bufferAttribute = new THREE.BufferAttribute(bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized);
-				}
+				return Promise.resolve( skinEntry );
 
-				for (var i = 0, il = sparseIndices.length; i < il; i++) {
-					var index = sparseIndices[i];
-					bufferAttribute.setX(index, sparseValues[i * itemSize]);
-					if (itemSize >= 2) bufferAttribute.setY(index, sparseValues[i * itemSize + 1]);
-					if (itemSize >= 3) bufferAttribute.setZ(index, sparseValues[i * itemSize + 2]);
-					if (itemSize >= 4) bufferAttribute.setW(index, sparseValues[i * itemSize + 3]);
-					if (itemSize >= 5) throw new Error('THREE.GLTFLoader: Unsupported itemSize in sparse THREE.BufferAttribute.');
-				}
 			}
 
-			return bufferAttribute;
-		});
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
-	 * @param {number} textureIndex
-	 * @return {Promise<THREE.Texture>}
-	 */
+			return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) {
 
+				skinEntry.inverseBindMatrices = accessor;
+				return skinEntry;
 
-	GLTFParser.prototype.loadTexture = function (textureIndex) {
-		var json = this.json;
-		var options = this.options;
-		var textureDef = json.textures[textureIndex];
-		var source = json.images[textureDef.source];
-		var loader = this.textureLoader;
+			} );
 
-		if (source.uri) {
-			var handler = options.manager.getHandler(source.uri);
-			if (handler !== null) loader = handler;
-		}
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations
+	 * @param {number} animationIndex
+	 * @return {Promise<AnimationClip>}
+	 */
 
-		return this.loadTextureImage(textureIndex, source, loader);
-	};
-
-	GLTFParser.prototype.loadTextureImage = function (textureIndex, source, loader) {
-		var parser = this;
-		var json = this.json;
-		var options = this.options;
-		var textureDef = json.textures[textureIndex];
-		var URL = self.URL || self.webkitURL;
-		var sourceURI = source.uri;
-		var isObjectURL = false;
-		var hasAlpha = true;
-		if (source.mimeType === 'image/jpeg') hasAlpha = false;
-
-		if (source.bufferView !== undefined) {
-			// Load binary image data from bufferView, if provided.
-			sourceURI = parser.getDependency('bufferView', source.bufferView).then(function (bufferView) {
-				if (source.mimeType === 'image/png') {
-					// Inspect the PNG 'IHDR' chunk to determine whether the image could have an
-					// alpha channel. This check is conservative — the image could have an alpha
-					// channel with all values == 1, and the indexed type (colorType == 3) only
-					// sometimes contains alpha.
-					//
-					// https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header
-					var colorType = new DataView(bufferView, 25, 1).getUint8(0, false);
-					hasAlpha = colorType === 6 || colorType === 4 || colorType === 3;
-				}
 
-				isObjectURL = true;
-				var blob = new Blob([bufferView], {
-					type: source.mimeType
-				});
-				sourceURI = URL.createObjectURL(blob);
-				return sourceURI;
-			});
-		} else if (source.uri === undefined) {
-			throw new Error('THREE.GLTFLoader: Image ' + textureIndex + ' is missing URI and bufferView');
-		}
+		GLTFParser.prototype.loadAnimation = function ( animationIndex ) {
 
-		return Promise.resolve(sourceURI).then(function (sourceURI) {
-			return new Promise(function (resolve, reject) {
-				var onLoad = resolve;
+			var json = this.json;
+			var animationDef = json.animations[ animationIndex ];
+			var pendingNodes = [];
+			var pendingInputAccessors = [];
+			var pendingOutputAccessors = [];
+			var pendingSamplers = [];
+			var pendingTargets = [];
+
+			for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) {
+
+				var channel = animationDef.channels[ i ];
+				var sampler = animationDef.samplers[ channel.sampler ];
+				var target = channel.target;
+				var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated.
+
+				var input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input;
+				var output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output;
+				pendingNodes.push( this.getDependency( 'node', name ) );
+				pendingInputAccessors.push( this.getDependency( 'accessor', input ) );
+				pendingOutputAccessors.push( this.getDependency( 'accessor', output ) );
+				pendingSamplers.push( sampler );
+				pendingTargets.push( target );
 
-				if (loader.isImageBitmapLoader === true) {
-					onLoad = function (imageBitmap) {
-						resolve(new THREE.CanvasTexture(imageBitmap));
-					};
-				}
+			}
 
-				loader.load(resolveURL(sourceURI, options.path), onLoad, undefined, reject);
-			});
-		}).then(function (texture) {
-			// Clean up resources and configure Texture.
-			if (isObjectURL === true) {
-				URL.revokeObjectURL(sourceURI);
-			}
-
-			texture.flipY = false;
-			if (textureDef.name) texture.name = textureDef.name; // When there is definitely no alpha channel in the texture, set THREE.RGBFormat to save space.
-
-			if (!hasAlpha) texture.format = THREE.RGBFormat;
-			var samplers = json.samplers || {};
-			var sampler = samplers[textureDef.sampler] || {};
-			texture.magFilter = WEBGL_FILTERS[sampler.magFilter] || THREE.LinearFilter;
-			texture.minFilter = WEBGL_FILTERS[sampler.minFilter] || THREE.LinearMipmapLinearFilter;
-			texture.wrapS = WEBGL_WRAPPINGS[sampler.wrapS] || THREE.RepeatWrapping;
-			texture.wrapT = WEBGL_WRAPPINGS[sampler.wrapT] || THREE.RepeatWrapping;
-			parser.associations.set(texture, {
-				type: 'textures',
-				index: textureIndex
-			});
-			return texture;
-		});
-	};
-	/**
-	 * Asynchronously assigns a texture to the given material parameters.
-	 * @param {Object} materialParams
-	 * @param {string} mapName
-	 * @param {Object} mapDef
-	 * @return {Promise}
-	 */
+			return Promise.all( [ Promise.all( pendingNodes ), Promise.all( pendingInputAccessors ), Promise.all( pendingOutputAccessors ), Promise.all( pendingSamplers ), Promise.all( pendingTargets ) ] ).then( function ( dependencies ) {
 
+				var nodes = dependencies[ 0 ];
+				var inputAccessors = dependencies[ 1 ];
+				var outputAccessors = dependencies[ 2 ];
+				var samplers = dependencies[ 3 ];
+				var targets = dependencies[ 4 ];
+				var tracks = [];
 
-	GLTFParser.prototype.assignTexture = function (materialParams, mapName, mapDef) {
-		var parser = this;
-		return this.getDependency('texture', mapDef.index).then(function (texture) {
-			// Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured
-			// However, we will copy UV set 0 to UV set 1 on demand for aoMap
-			if (mapDef.texCoord !== undefined && mapDef.texCoord != 0 && !(mapName === 'aoMap' && mapDef.texCoord == 1)) {
-				console.warn('THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.');
-			}
+				for ( var i = 0, il = nodes.length; i < il; i ++ ) {
 
-			if (parser.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM]) {
-				var transform = mapDef.extensions !== undefined ? mapDef.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM] : undefined;
+					var node = nodes[ i ];
+					var inputAccessor = inputAccessors[ i ];
+					var outputAccessor = outputAccessors[ i ];
+					var sampler = samplers[ i ];
+					var target = targets[ i ];
+					if ( node === undefined ) continue;
+					node.updateMatrix();
+					node.matrixAutoUpdate = true;
+					var TypedKeyframeTrack;
 
-				if (transform) {
-					var gltfReference = parser.associations.get(texture);
-					texture = parser.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM].extendTexture(texture, transform);
-					parser.associations.set(texture, gltfReference);
-				}
-			}
+					switch ( PATH_PROPERTIES[ target.path ] ) {
 
-			materialParams[mapName] = texture;
-		});
-	};
-	/**
-	 * Assigns final material to a THREE.Mesh, THREE.Line, or THREE.Points instance. The instance
-	 * already has a material (generated from the glTF material options alone)
-	 * but reuse of the same glTF material may require multiple threejs materials
-	 * to accommodate different primitive types, defines, etc. New materials will
-	 * be created if necessary, and reused from a cache.
-	 * @param	{Object3D} mesh THREE.Mesh, THREE.Line, or THREE.Points instance.
-	 */
+						case PATH_PROPERTIES.weights:
+							TypedKeyframeTrack = THREE.NumberKeyframeTrack;
+							break;
 
+						case PATH_PROPERTIES.rotation:
+							TypedKeyframeTrack = THREE.QuaternionKeyframeTrack;
+							break;
 
-	GLTFParser.prototype.assignFinalMaterial = function (mesh) {
-		var geometry = mesh.geometry;
-		var material = mesh.material;
-		var useVertexTangents = geometry.attributes.tangent !== undefined;
-		var useVertexColors = geometry.attributes.color !== undefined;
-		var useFlatShading = geometry.attributes.normal === undefined;
-		var useSkinning = mesh.isSkinnedMesh === true;
-		var useMorphTargets = Object.keys(geometry.morphAttributes).length > 0;
-		var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined;
-
-		if (mesh.isPoints) {
-			var cacheKey = 'PointsMaterial:' + material.uuid;
-			var pointsMaterial = this.cache.get(cacheKey);
-
-			if (!pointsMaterial) {
-				pointsMaterial = new THREE.PointsMaterial();
-				THREE.Material.prototype.copy.call(pointsMaterial, material);
-				pointsMaterial.color.copy(material.color);
-				pointsMaterial.map = material.map;
-				pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px
-
-				this.cache.add(cacheKey, pointsMaterial);
-			}
-
-			material = pointsMaterial;
-		} else if (mesh.isLine) {
-			var cacheKey = 'LineBasicMaterial:' + material.uuid;
-			var lineMaterial = this.cache.get(cacheKey);
-
-			if (!lineMaterial) {
-				lineMaterial = new THREE.LineBasicMaterial();
-				THREE.Material.prototype.copy.call(lineMaterial, material);
-				lineMaterial.color.copy(material.color);
-				this.cache.add(cacheKey, lineMaterial);
-			}
-
-			material = lineMaterial;
-		} // Clone the material if it will be modified
-
-
-		if (useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets) {
-			var cacheKey = 'ClonedMaterial:' + material.uuid + ':';
-			if (material.isGLTFSpecularGlossinessMaterial) cacheKey += 'specular-glossiness:';
-			if (useSkinning) cacheKey += 'skinning:';
-			if (useVertexTangents) cacheKey += 'vertex-tangents:';
-			if (useVertexColors) cacheKey += 'vertex-colors:';
-			if (useFlatShading) cacheKey += 'flat-shading:';
-			if (useMorphTargets) cacheKey += 'morph-targets:';
-			if (useMorphNormals) cacheKey += 'morph-normals:';
-			var cachedMaterial = this.cache.get(cacheKey);
-
-			if (!cachedMaterial) {
-				cachedMaterial = material.clone();
-				if (useSkinning) cachedMaterial.skinning = true;
-				if (useVertexColors) cachedMaterial.vertexColors = true;
-				if (useFlatShading) cachedMaterial.flatShading = true;
-				if (useMorphTargets) cachedMaterial.morphTargets = true;
-				if (useMorphNormals) cachedMaterial.morphNormals = true;
-
-				if (useVertexTangents) {
-					cachedMaterial.vertexTangents = true; // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
-
-					if (cachedMaterial.normalScale) cachedMaterial.normalScale.y *= -1;
-					if (cachedMaterial.clearcoatNormalScale) cachedMaterial.clearcoatNormalScale.y *= -1;
-				}
+						case PATH_PROPERTIES.position:
+						case PATH_PROPERTIES.scale:
+						default:
+							TypedKeyframeTrack = THREE.VectorKeyframeTrack;
+							break;
 
-				this.cache.add(cacheKey, cachedMaterial);
-				this.associations.set(cachedMaterial, this.associations.get(material));
-			}
+					}
 
-			material = cachedMaterial;
-		} // workarounds for mesh and geometry
+					var targetName = node.name ? node.name : node.uuid;
+					var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear;
+					var targetNames = [];
 
+					if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) {
 
-		if (material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined) {
-			geometry.setAttribute('uv2', geometry.attributes.uv);
-		}
+						// Node may be a THREE.Group (glTF mesh with several primitives) or a THREE.Mesh.
+						node.traverse( function ( object ) {
 
-		mesh.material = material;
-	};
+							if ( object.isMesh === true && object.morphTargetInfluences ) {
 
-	GLTFParser.prototype.getMaterialType = function ()
-	/* materialIndex */
-	{
-		return THREE.MeshStandardMaterial;
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
-	 * @param {number} materialIndex
-	 * @return {Promise<Material>}
-	 */
+								targetNames.push( object.name ? object.name : object.uuid );
 
+							}
 
-	GLTFParser.prototype.loadMaterial = function (materialIndex) {
-		var parser = this;
-		var json = this.json;
-		var extensions = this.extensions;
-		var materialDef = json.materials[materialIndex];
-		var materialType;
-		var materialParams = {};
-		var materialExtensions = materialDef.extensions || {};
-		var pending = [];
-
-		if (materialExtensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS]) {
-			var sgExtension = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS];
-			materialType = sgExtension.getMaterialType();
-			pending.push(sgExtension.extendParams(materialParams, materialDef, parser));
-		} else if (materialExtensions[EXTENSIONS.KHR_MATERIALS_UNLIT]) {
-			var kmuExtension = extensions[EXTENSIONS.KHR_MATERIALS_UNLIT];
-			materialType = kmuExtension.getMaterialType();
-			pending.push(kmuExtension.extendParams(materialParams, materialDef, parser));
-		} else {
-			// Specification:
-			// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material
-			var metallicRoughness = materialDef.pbrMetallicRoughness || {};
-			materialParams.color = new THREE.Color(1.0, 1.0, 1.0);
-			materialParams.opacity = 1.0;
+						} );
 
-			if (Array.isArray(metallicRoughness.baseColorFactor)) {
-				var array = metallicRoughness.baseColorFactor;
-				materialParams.color.fromArray(array);
-				materialParams.opacity = array[3];
-			}
+					} else {
 
-			if (metallicRoughness.baseColorTexture !== undefined) {
-				pending.push(parser.assignTexture(materialParams, 'map', metallicRoughness.baseColorTexture));
-			}
+						targetNames.push( targetName );
 
-			materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
-			materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;
+					}
 
-			if (metallicRoughness.metallicRoughnessTexture !== undefined) {
-				pending.push(parser.assignTexture(materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture));
-				pending.push(parser.assignTexture(materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture));
-			}
+					var outputArray = outputAccessor.array;
 
-			materialType = this._invokeOne(function (ext) {
-				return ext.getMaterialType && ext.getMaterialType(materialIndex);
-			});
-			pending.push(Promise.all(this._invokeAll(function (ext) {
-				return ext.extendMaterialParams && ext.extendMaterialParams(materialIndex, materialParams);
-			})));
-		}
+					if ( outputAccessor.normalized ) {
 
-		if (materialDef.doubleSided === true) {
-			materialParams.side = THREE.DoubleSide;
-		}
+						var scale = getNormalizedComponentScale( outputArray.constructor );
+						var scaled = new Float32Array( outputArray.length );
 
-		var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE;
+						for ( var j = 0, jl = outputArray.length; j < jl; j ++ ) {
 
-		if (alphaMode === ALPHA_MODES.BLEND) {
-			materialParams.transparent = true; // See: https://github.com/mrdoob/three.js/issues/17706
+							scaled[ j ] = outputArray[ j ] * scale;
 
-			materialParams.depthWrite = false;
-		} else {
-			materialParams.transparent = false;
+						}
 
-			if (alphaMode === ALPHA_MODES.MASK) {
-				materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;
-			}
-		}
+						outputArray = scaled;
 
-		if (materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial) {
-			pending.push(parser.assignTexture(materialParams, 'normalMap', materialDef.normalTexture)); // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
+					}
 
-			materialParams.normalScale = new THREE.Vector2(1, -1);
+					for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) {
 
-			if (materialDef.normalTexture.scale !== undefined) {
-				materialParams.normalScale.set(materialDef.normalTexture.scale, -materialDef.normalTexture.scale);
-			}
-		}
+						var track = new TypedKeyframeTrack( targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], inputAccessor.array, outputArray, interpolation ); // Override interpolation with custom factory method.
 
-		if (materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial) {
-			pending.push(parser.assignTexture(materialParams, 'aoMap', materialDef.occlusionTexture));
+						if ( sampler.interpolation === 'CUBICSPLINE' ) {
 
-			if (materialDef.occlusionTexture.strength !== undefined) {
-				materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;
-			}
-		}
+							track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) {
 
-		if (materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial) {
-			materialParams.emissive = new THREE.Color().fromArray(materialDef.emissiveFactor);
-		}
+								// A CUBICSPLINE keyframe in glTF has three output values for each input value,
+								// representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize()
+								// must be divided by three to get the interpolant's sampleSize argument.
+								return new GLTFCubicSplineInterpolant( this.times, this.values, this.getValueSize() / 3, result );
 
-		if (materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial) {
-			pending.push(parser.assignTexture(materialParams, 'emissiveMap', materialDef.emissiveTexture));
-		}
+							}; // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants.
 
-		return Promise.all(pending).then(function () {
-			var material;
 
-			if (materialType === GLTFMeshStandardSGMaterial) {
-				material = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].createMaterial(materialParams);
-			} else {
-				material = new materialType(materialParams);
-			}
+							track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true;
 
-			if (materialDef.name) material.name = materialDef.name; // baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding.
+						}
 
-			if (material.map) material.map.encoding = THREE.sRGBEncoding;
-			if (material.emissiveMap) material.emissiveMap.encoding = THREE.sRGBEncoding;
-			assignExtrasToUserData(material, materialDef);
-			parser.associations.set(material, {
-				type: 'materials',
-				index: materialIndex
-			});
-			if (materialDef.extensions) addUnknownExtensionsToUserData(extensions, material, materialDef);
-			return material;
-		});
-	};
-	/** When THREE.Object3D instances are targeted by animation, they need unique names. */
+						tracks.push( track );
 
+					}
 
-	GLTFParser.prototype.createUniqueName = function (originalName) {
-		var sanitizedName = THREE.PropertyBinding.sanitizeNodeName(originalName || '');
-		var name = sanitizedName;
+				}
 
-		for (var i = 1; this.nodeNamesUsed[name]; ++i) {
-			name = sanitizedName + '_' + i;
-		}
+				var name = animationDef.name ? animationDef.name : 'animation_' + animationIndex;
+				return new THREE.AnimationClip( name, undefined, tracks );
 
-		this.nodeNamesUsed[name] = true;
-		return name;
-	};
-	/**
-	 * @param {BufferGeometry} geometry
-	 * @param {GLTF.Primitive} primitiveDef
-	 * @param {GLTFParser} parser
-	 */
+			} );
 
+		};
 
-	function computeBounds(geometry, primitiveDef, parser) {
-		var attributes = primitiveDef.attributes;
-		var box = new THREE.Box3();
+		GLTFParser.prototype.createNodeMesh = function ( nodeIndex ) {
 
-		if (attributes.POSITION !== undefined) {
-			var accessor = parser.json.accessors[attributes.POSITION];
-			var min = accessor.min;
-			var max = accessor.max; // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
+			var json = this.json;
+			var parser = this;
+			var nodeDef = json.nodes[ nodeIndex ];
+			if ( nodeDef.mesh === undefined ) return null;
+			return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) {
 
-			if (min !== undefined && max !== undefined) {
-				box.set(new THREE.Vector3(min[0], min[1], min[2]), new THREE.Vector3(max[0], max[1], max[2]));
+				var node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); // if weights are provided on the node, override weights on the mesh.
 
-				if (accessor.normalized) {
-					var boxScale = getNormalizedComponentScale(WEBGL_COMPONENT_TYPES[accessor.componentType]);
-					box.min.multiplyScalar(boxScale);
-					box.max.multiplyScalar(boxScale);
-				}
-			} else {
-				console.warn('THREE.GLTFLoader: Missing min/max properties for accessor POSITION.');
-				return;
-			}
-		} else {
-			return;
-		}
 
-		var targets = primitiveDef.targets;
+				if ( nodeDef.weights !== undefined ) {
 
-		if (targets !== undefined) {
-			var maxDisplacement = new THREE.Vector3();
-			var vector = new THREE.Vector3();
+					node.traverse( function ( o ) {
 
-			for (var i = 0, il = targets.length; i < il; i++) {
-				var target = targets[i];
+						if ( ! o.isMesh ) return;
 
-				if (target.POSITION !== undefined) {
-					var accessor = parser.json.accessors[target.POSITION];
-					var min = accessor.min;
-					var max = accessor.max; // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.
+						for ( var i = 0, il = nodeDef.weights.length; i < il; i ++ ) {
 
-					if (min !== undefined && max !== undefined) {
-						// we need to get max of absolute components because target weight is [-1,1]
-						vector.setX(Math.max(Math.abs(min[0]), Math.abs(max[0])));
-						vector.setY(Math.max(Math.abs(min[1]), Math.abs(max[1])));
-						vector.setZ(Math.max(Math.abs(min[2]), Math.abs(max[2])));
+							o.morphTargetInfluences[ i ] = nodeDef.weights[ i ];
 
-						if (accessor.normalized) {
-							var boxScale = getNormalizedComponentScale(WEBGL_COMPONENT_TYPES[accessor.componentType]);
-							vector.multiplyScalar(boxScale);
-						} // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative
-						// to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets
-						// are used to implement key-frame animations and as such only two are active at a time - this results in very large
-						// boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size.
+						}
 
+					} );
 
-						maxDisplacement.max(vector);
-					} else {
-						console.warn('THREE.GLTFLoader: Missing min/max properties for accessor POSITION.');
-					}
 				}
-			} // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets.
 
+				return node;
 
-			box.expandByVector(maxDisplacement);
-		}
+			} );
 
-		geometry.boundingBox = box;
-		var sphere = new THREE.Sphere();
-		box.getCenter(sphere.center);
-		sphere.radius = box.min.distanceTo(box.max) / 2;
-		geometry.boundingSphere = sphere;
-	}
-	/**
-	 * @param {BufferGeometry} geometry
-	 * @param {GLTF.Primitive} primitiveDef
-	 * @param {GLTFParser} parser
-	 * @return {Promise<BufferGeometry>}
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy
+	 * @param {number} nodeIndex
+	 * @return {Promise<Object3D>}
 	 */
 
 
-	function addPrimitiveAttributes(geometry, primitiveDef, parser) {
-		var attributes = primitiveDef.attributes;
-		var pending = [];
+		GLTFParser.prototype.loadNode = function ( nodeIndex ) {
 
-		function assignAttributeAccessor(accessorIndex, attributeName) {
-			return parser.getDependency('accessor', accessorIndex).then(function (accessor) {
-				geometry.setAttribute(attributeName, accessor);
-			});
-		}
+			var json = this.json;
+			var extensions = this.extensions;
+			var parser = this;
+			var nodeDef = json.nodes[ nodeIndex ]; // reserve node's name before its dependencies, so the root has the intended name.
 
-		for (var gltfAttributeName in attributes) {
-			var threeAttributeName = ATTRIBUTES[gltfAttributeName] || gltfAttributeName.toLowerCase(); // Skip attributes already provided by e.g. Draco extension.
+			var nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : '';
+			return function () {
 
-			if (threeAttributeName in geometry.attributes) continue;
-			pending.push(assignAttributeAccessor(attributes[gltfAttributeName], threeAttributeName));
-		}
+				var pending = [];
 
-		if (primitiveDef.indices !== undefined && !geometry.index) {
-			var accessor = parser.getDependency('accessor', primitiveDef.indices).then(function (accessor) {
-				geometry.setIndex(accessor);
-			});
-			pending.push(accessor);
-		}
+				var meshPromise = parser._invokeOne( function ( ext ) {
 
-		assignExtrasToUserData(geometry, primitiveDef);
-		computeBounds(geometry, primitiveDef, parser);
-		return Promise.all(pending).then(function () {
-			return primitiveDef.targets !== undefined ? addMorphTargets(geometry, primitiveDef.targets, parser) : geometry;
-		});
-	}
-	/**
-	 * @param {BufferGeometry} geometry
-	 * @param {Number} drawMode
-	 * @return {BufferGeometry}
-	 */
+					return ext.createNodeMesh && ext.createNodeMesh( nodeIndex );
 
+				} );
 
-	function toTrianglesDrawMode(geometry, drawMode) {
-		var index = geometry.getIndex(); // generate index if not present
+				if ( meshPromise ) {
 
-		if (index === null) {
-			var indices = [];
-			var position = geometry.getAttribute('position');
+					pending.push( meshPromise );
 
-			if (position !== undefined) {
-				for (var i = 0; i < position.count; i++) {
-					indices.push(i);
 				}
 
-				geometry.setIndex(indices);
-				index = geometry.getIndex();
-			} else {
-				console.error('THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.');
-				return geometry;
-			}
-		} //
+				if ( nodeDef.camera !== undefined ) {
 
+					pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) {
 
-		var numberOfTriangles = index.count - 2;
-		var newIndices = [];
+						return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera );
 
-		if (drawMode === THREE.TriangleFanDrawMode) {
-			// gl.TRIANGLE_FAN
-			for (var i = 1; i <= numberOfTriangles; i++) {
-				newIndices.push(index.getX(0));
-				newIndices.push(index.getX(i));
-				newIndices.push(index.getX(i + 1));
-			}
-		} else {
-			// gl.TRIANGLE_STRIP
-			for (var i = 0; i < numberOfTriangles; i++) {
-				if (i % 2 === 0) {
-					newIndices.push(index.getX(i));
-					newIndices.push(index.getX(i + 1));
-					newIndices.push(index.getX(i + 2));
-				} else {
-					newIndices.push(index.getX(i + 2));
-					newIndices.push(index.getX(i + 1));
-					newIndices.push(index.getX(i));
-				}
-			}
-		}
+					} ) );
 
-		if (newIndices.length / 3 !== numberOfTriangles) {
-			console.error('THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.');
-		} // build final geometry
+				}
 
+				parser._invokeAll( function ( ext ) {
 
-		var newGeometry = geometry.clone();
-		newGeometry.setIndex(newIndices);
-		return newGeometry;
-	}
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
-	 *
-	 * Creates BufferGeometries from primitives.
-	 *
-	 * @param {Array<GLTF.Primitive>} primitives
-	 * @return {Promise<Array<BufferGeometry>>}
-	 */
+					return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex );
 
+				} ).forEach( function ( promise ) {
 
-	GLTFParser.prototype.loadGeometries = function (primitives) {
-		var parser = this;
-		var extensions = this.extensions;
-		var cache = this.primitiveCache;
+					pending.push( promise );
 
-		function createDracoPrimitive(primitive) {
-			return extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION].decodePrimitive(primitive, parser).then(function (geometry) {
-				return addPrimitiveAttributes(geometry, primitive, parser);
-			});
-		}
+				} );
 
-		var pending = [];
+				return Promise.all( pending );
 
-		for (var i = 0, il = primitives.length; i < il; i++) {
-			var primitive = primitives[i];
-			var cacheKey = createPrimitiveKey(primitive); // See if we've already created this geometry
+			}().then( function ( objects ) {
 
-			var cached = cache[cacheKey];
+				var node; // .isBone isn't in glTF spec. See ._markDefs
 
-			if (cached) {
-				// Use the cached geometry if it exists
-				pending.push(cached.promise);
-			} else {
-				var geometryPromise;
+				if ( nodeDef.isBone === true ) {
 
-				if (primitive.extensions && primitive.extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION]) {
-					// Use DRACO geometry if available
-					geometryPromise = createDracoPrimitive(primitive);
-				} else {
-					// Otherwise create a new geometry
-					geometryPromise = addPrimitiveAttributes(new THREE.BufferGeometry(), primitive, parser);
-				} // Cache this geometry
+					node = new THREE.Bone();
 
+				} else if ( objects.length > 1 ) {
 
-				cache[cacheKey] = {
-					primitive: primitive,
-					promise: geometryPromise
-				};
-				pending.push(geometryPromise);
-			}
-		}
+					node = new THREE.Group();
 
-		return Promise.all(pending);
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
-	 * @param {number} meshIndex
-	 * @return {Promise<Group|Mesh|SkinnedMesh>}
-	 */
+				} else if ( objects.length === 1 ) {
 
+					node = objects[ 0 ];
 
-	GLTFParser.prototype.loadMesh = function (meshIndex) {
-		var parser = this;
-		var json = this.json;
-		var extensions = this.extensions;
-		var meshDef = json.meshes[meshIndex];
-		var primitives = meshDef.primitives;
-		var pending = [];
+				} else {
 
-		for (var i = 0, il = primitives.length; i < il; i++) {
-			var material = primitives[i].material === undefined ? createDefaultMaterial(this.cache) : this.getDependency('material', primitives[i].material);
-			pending.push(material);
-		}
+					node = new THREE.Object3D();
 
-		pending.push(parser.loadGeometries(primitives));
-		return Promise.all(pending).then(function (results) {
-			var materials = results.slice(0, results.length - 1);
-			var geometries = results[results.length - 1];
-			var meshes = [];
+				}
 
-			for (var i = 0, il = geometries.length; i < il; i++) {
-				var geometry = geometries[i];
-				var primitive = primitives[i]; // 1. create THREE.Mesh
+				if ( node !== objects[ 0 ] ) {
 
-				var mesh;
-				var material = materials[i];
+					for ( var i = 0, il = objects.length; i < il; i ++ ) {
 
-				if (primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || primitive.mode === undefined) {
-					// .isSkinnedMesh isn't in glTF spec. See ._markDefs()
-					mesh = meshDef.isSkinnedMesh === true ? new THREE.SkinnedMesh(geometry, material) : new THREE.Mesh(geometry, material);
+						node.add( objects[ i ] );
 
-					if (mesh.isSkinnedMesh === true && !mesh.geometry.attributes.skinWeight.normalized) {
-						// we normalize floating point skin weight array to fix malformed assets (see #15319)
-						// it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
-						mesh.normalizeSkinWeights();
 					}
 
-					if (primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP) {
-						mesh.geometry = toTrianglesDrawMode(mesh.geometry, THREE.TriangleStripDrawMode);
-					} else if (primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN) {
-						mesh.geometry = toTrianglesDrawMode(mesh.geometry, THREE.TriangleFanDrawMode);
-					}
-				} else if (primitive.mode === WEBGL_CONSTANTS.LINES) {
-					mesh = new THREE.LineSegments(geometry, material);
-				} else if (primitive.mode === WEBGL_CONSTANTS.LINE_STRIP) {
-					mesh = new THREE.Line(geometry, material);
-				} else if (primitive.mode === WEBGL_CONSTANTS.LINE_LOOP) {
-					mesh = new THREE.LineLoop(geometry, material);
-				} else if (primitive.mode === WEBGL_CONSTANTS.POINTS) {
-					mesh = new THREE.Points(geometry, material);
-				} else {
-					throw new Error('THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode);
 				}
 
-				if (Object.keys(mesh.geometry.morphAttributes).length > 0) {
-					updateMorphTargets(mesh, meshDef);
-				}
+				if ( nodeDef.name ) {
 
-				mesh.name = parser.createUniqueName(meshDef.name || 'mesh_' + meshIndex);
-				assignExtrasToUserData(mesh, meshDef);
-				if (primitive.extensions) addUnknownExtensionsToUserData(extensions, mesh, primitive);
-				parser.assignFinalMaterial(mesh);
-				meshes.push(mesh);
-			}
+					node.userData.name = nodeDef.name;
+					node.name = nodeName;
 
-			if (meshes.length === 1) {
-				return meshes[0];
-			}
+				}
 
-			var group = new THREE.Group();
+				assignExtrasToUserData( node, nodeDef );
+				if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef );
 
-			for (var i = 0, il = meshes.length; i < il; i++) {
-				group.add(meshes[i]);
-			}
+				if ( nodeDef.matrix !== undefined ) {
 
-			return group;
-		});
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
-	 * @param {number} cameraIndex
-	 * @return {Promise<THREE.Camera>}
-	 */
+					var matrix = new THREE.Matrix4();
+					matrix.fromArray( nodeDef.matrix );
+					node.applyMatrix4( matrix );
 
+				} else {
 
-	GLTFParser.prototype.loadCamera = function (cameraIndex) {
-		var camera;
-		var cameraDef = this.json.cameras[cameraIndex];
-		var params = cameraDef[cameraDef.type];
+					if ( nodeDef.translation !== undefined ) {
 
-		if (!params) {
-			console.warn('THREE.GLTFLoader: Missing camera parameters.');
-			return;
-		}
+						node.position.fromArray( nodeDef.translation );
 
-		if (cameraDef.type === 'perspective') {
-			camera = new THREE.PerspectiveCamera(THREE.MathUtils.radToDeg(params.yfov), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6);
-		} else if (cameraDef.type === 'orthographic') {
-			camera = new THREE.OrthographicCamera(-params.xmag, params.xmag, params.ymag, -params.ymag, params.znear, params.zfar);
-		}
+					}
 
-		if (cameraDef.name) camera.name = this.createUniqueName(cameraDef.name);
-		assignExtrasToUserData(camera, cameraDef);
-		return Promise.resolve(camera);
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
-	 * @param {number} skinIndex
-	 * @return {Promise<Object>}
-	 */
+					if ( nodeDef.rotation !== undefined ) {
 
+						node.quaternion.fromArray( nodeDef.rotation );
 
-	GLTFParser.prototype.loadSkin = function (skinIndex) {
-		var skinDef = this.json.skins[skinIndex];
-		var skinEntry = {
-			joints: skinDef.joints
-		};
+					}
 
-		if (skinDef.inverseBindMatrices === undefined) {
-			return Promise.resolve(skinEntry);
-		}
+					if ( nodeDef.scale !== undefined ) {
 
-		return this.getDependency('accessor', skinDef.inverseBindMatrices).then(function (accessor) {
-			skinEntry.inverseBindMatrices = accessor;
-			return skinEntry;
-		});
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations
-	 * @param {number} animationIndex
-	 * @return {Promise<AnimationClip>}
-	 */
+						node.scale.fromArray( nodeDef.scale );
 
+					}
 
-	GLTFParser.prototype.loadAnimation = function (animationIndex) {
-		var json = this.json;
-		var animationDef = json.animations[animationIndex];
-		var pendingNodes = [];
-		var pendingInputAccessors = [];
-		var pendingOutputAccessors = [];
-		var pendingSamplers = [];
-		var pendingTargets = [];
-
-		for (var i = 0, il = animationDef.channels.length; i < il; i++) {
-			var channel = animationDef.channels[i];
-			var sampler = animationDef.samplers[channel.sampler];
-			var target = channel.target;
-			var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated.
-
-			var input = animationDef.parameters !== undefined ? animationDef.parameters[sampler.input] : sampler.input;
-			var output = animationDef.parameters !== undefined ? animationDef.parameters[sampler.output] : sampler.output;
-			pendingNodes.push(this.getDependency('node', name));
-			pendingInputAccessors.push(this.getDependency('accessor', input));
-			pendingOutputAccessors.push(this.getDependency('accessor', output));
-			pendingSamplers.push(sampler);
-			pendingTargets.push(target);
-		}
+				}
 
-		return Promise.all([Promise.all(pendingNodes), Promise.all(pendingInputAccessors), Promise.all(pendingOutputAccessors), Promise.all(pendingSamplers), Promise.all(pendingTargets)]).then(function (dependencies) {
-			var nodes = dependencies[0];
-			var inputAccessors = dependencies[1];
-			var outputAccessors = dependencies[2];
-			var samplers = dependencies[3];
-			var targets = dependencies[4];
-			var tracks = [];
-
-			for (var i = 0, il = nodes.length; i < il; i++) {
-				var node = nodes[i];
-				var inputAccessor = inputAccessors[i];
-				var outputAccessor = outputAccessors[i];
-				var sampler = samplers[i];
-				var target = targets[i];
-				if (node === undefined) continue;
-				node.updateMatrix();
-				node.matrixAutoUpdate = true;
-				var TypedKeyframeTrack;
-
-				switch (PATH_PROPERTIES[target.path]) {
-					case PATH_PROPERTIES.weights:
-						TypedKeyframeTrack = THREE.NumberKeyframeTrack;
-						break;
+				parser.associations.set( node, {
+					type: 'nodes',
+					index: nodeIndex
+				} );
+				return node;
 
-					case PATH_PROPERTIES.rotation:
-						TypedKeyframeTrack = THREE.QuaternionKeyframeTrack;
-						break;
+			} );
 
-					case PATH_PROPERTIES.position:
-					case PATH_PROPERTIES.scale:
-					default:
-						TypedKeyframeTrack = THREE.VectorKeyframeTrack;
-						break;
-				}
+		};
+		/**
+	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes
+	 * @param {number} sceneIndex
+	 * @return {Promise<Group>}
+	 */
 
-				var targetName = node.name ? node.name : node.uuid;
-				var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[sampler.interpolation] : THREE.InterpolateLinear;
-				var targetNames = [];
 
-				if (PATH_PROPERTIES[target.path] === PATH_PROPERTIES.weights) {
-					// Node may be a THREE.Group (glTF mesh with several primitives) or a THREE.Mesh.
-					node.traverse(function (object) {
-						if (object.isMesh === true && object.morphTargetInfluences) {
-							targetNames.push(object.name ? object.name : object.uuid);
-						}
-					});
-				} else {
-					targetNames.push(targetName);
-				}
+		GLTFParser.prototype.loadScene = function () {
 
-				var outputArray = outputAccessor.array;
+			// scene node hierachy builder
+			function buildNodeHierachy( nodeId, parentObject, json, parser ) {
 
-				if (outputAccessor.normalized) {
-					var scale = getNormalizedComponentScale(outputArray.constructor);
-					var scaled = new Float32Array(outputArray.length);
+				var nodeDef = json.nodes[ nodeId ];
+				return parser.getDependency( 'node', nodeId ).then( function ( node ) {
 
-					for (var j = 0, jl = outputArray.length; j < jl; j++) {
-						scaled[j] = outputArray[j] * scale;
-					}
+					if ( nodeDef.skin === undefined ) return node; // build skeleton here as well
 
-					outputArray = scaled;
-				}
+					var skinEntry;
+					return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) {
 
-				for (var j = 0, jl = targetNames.length; j < jl; j++) {
-					var track = new TypedKeyframeTrack(targetNames[j] + '.' + PATH_PROPERTIES[target.path], inputAccessor.array, outputArray, interpolation); // Override interpolation with custom factory method.
+						skinEntry = skin;
+						var pendingJoints = [];
 
-					if (sampler.interpolation === 'CUBICSPLINE') {
-						track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline(result) {
-							// A CUBICSPLINE keyframe in glTF has three output values for each input value,
-							// representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize()
-							// must be divided by three to get the interpolant's sampleSize argument.
-							return new GLTFCubicSplineInterpolant(this.times, this.values, this.getValueSize() / 3, result);
-						}; // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants.
+						for ( var i = 0, il = skinEntry.joints.length; i < il; i ++ ) {
 
+							pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) );
 
-						track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true;
-					}
+						}
 
-					tracks.push(track);
-				}
-			}
+						return Promise.all( pendingJoints );
 
-			var name = animationDef.name ? animationDef.name : 'animation_' + animationIndex;
-			return new THREE.AnimationClip(name, undefined, tracks);
-		});
-	};
+					} ).then( function ( jointNodes ) {
 
-	GLTFParser.prototype.createNodeMesh = function (nodeIndex) {
-		var json = this.json;
-		var parser = this;
-		var nodeDef = json.nodes[nodeIndex];
-		if (nodeDef.mesh === undefined) return null;
-		return parser.getDependency('mesh', nodeDef.mesh).then(function (mesh) {
-			var node = parser._getNodeRef(parser.meshCache, nodeDef.mesh, mesh); // if weights are provided on the node, override weights on the mesh.
+						node.traverse( function ( mesh ) {
 
+							if ( ! mesh.isMesh ) return;
+							var bones = [];
+							var boneInverses = [];
 
-			if (nodeDef.weights !== undefined) {
-				node.traverse(function (o) {
-					if (!o.isMesh) return;
+							for ( var j = 0, jl = jointNodes.length; j < jl; j ++ ) {
 
-					for (var i = 0, il = nodeDef.weights.length; i < il; i++) {
-						o.morphTargetInfluences[i] = nodeDef.weights[i];
-					}
-				});
-			}
+								var jointNode = jointNodes[ j ];
 
-			return node;
-		});
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy
-	 * @param {number} nodeIndex
-	 * @return {Promise<Object3D>}
-	 */
+								if ( jointNode ) {
 
+									bones.push( jointNode );
+									var mat = new THREE.Matrix4();
 
-	GLTFParser.prototype.loadNode = function (nodeIndex) {
-		var json = this.json;
-		var extensions = this.extensions;
-		var parser = this;
-		var nodeDef = json.nodes[nodeIndex]; // reserve node's name before its dependencies, so the root has the intended name.
+									if ( skinEntry.inverseBindMatrices !== undefined ) {
 
-		var nodeName = nodeDef.name ? parser.createUniqueName(nodeDef.name) : '';
-		return function () {
-			var pending = [];
+										mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 );
 
-			var meshPromise = parser._invokeOne(function (ext) {
-				return ext.createNodeMesh && ext.createNodeMesh(nodeIndex);
-			});
+									}
 
-			if (meshPromise) {
-				pending.push(meshPromise);
-			}
+									boneInverses.push( mat );
 
-			if (nodeDef.camera !== undefined) {
-				pending.push(parser.getDependency('camera', nodeDef.camera).then(function (camera) {
-					return parser._getNodeRef(parser.cameraCache, nodeDef.camera, camera);
-				}));
-			}
+								} else {
 
-			parser._invokeAll(function (ext) {
-				return ext.createNodeAttachment && ext.createNodeAttachment(nodeIndex);
-			}).forEach(function (promise) {
-				pending.push(promise);
-			});
+									console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] );
 
-			return Promise.all(pending);
-		}().then(function (objects) {
-			var node; // .isBone isn't in glTF spec. See ._markDefs
+								}
 
-			if (nodeDef.isBone === true) {
-				node = new THREE.Bone();
-			} else if (objects.length > 1) {
-				node = new THREE.Group();
-			} else if (objects.length === 1) {
-				node = objects[0];
-			} else {
-				node = new THREE.Object3D();
-			}
+							}
 
-			if (node !== objects[0]) {
-				for (var i = 0, il = objects.length; i < il; i++) {
-					node.add(objects[i]);
-				}
-			}
+							mesh.bind( new THREE.Skeleton( bones, boneInverses ), mesh.matrixWorld );
 
-			if (nodeDef.name) {
-				node.userData.name = nodeDef.name;
-				node.name = nodeName;
-			}
+						} );
+						return node;
 
-			assignExtrasToUserData(node, nodeDef);
-			if (nodeDef.extensions) addUnknownExtensionsToUserData(extensions, node, nodeDef);
+					} );
 
-			if (nodeDef.matrix !== undefined) {
-				var matrix = new THREE.Matrix4();
-				matrix.fromArray(nodeDef.matrix);
-				node.applyMatrix4(matrix);
-			} else {
-				if (nodeDef.translation !== undefined) {
-					node.position.fromArray(nodeDef.translation);
-				}
+				} ).then( function ( node ) {
 
-				if (nodeDef.rotation !== undefined) {
-					node.quaternion.fromArray(nodeDef.rotation);
-				}
+					// build node hierachy
+					parentObject.add( node );
+					var pending = [];
 
-				if (nodeDef.scale !== undefined) {
-					node.scale.fromArray(nodeDef.scale);
-				}
-			}
+					if ( nodeDef.children ) {
 
-			parser.associations.set(node, {
-				type: 'nodes',
-				index: nodeIndex
-			});
-			return node;
-		});
-	};
-	/**
-	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes
-	 * @param {number} sceneIndex
-	 * @return {Promise<Group>}
-	 */
+						var children = nodeDef.children;
 
+						for ( var i = 0, il = children.length; i < il; i ++ ) {
 
-	GLTFParser.prototype.loadScene = function () {
-		// scene node hierachy builder
-		function buildNodeHierachy(nodeId, parentObject, json, parser) {
-			var nodeDef = json.nodes[nodeId];
-			return parser.getDependency('node', nodeId).then(function (node) {
-				if (nodeDef.skin === undefined) return node; // build skeleton here as well
+							var child = children[ i ];
+							pending.push( buildNodeHierachy( child, node, json, parser ) );
 
-				var skinEntry;
-				return parser.getDependency('skin', nodeDef.skin).then(function (skin) {
-					skinEntry = skin;
-					var pendingJoints = [];
+						}
 
-					for (var i = 0, il = skinEntry.joints.length; i < il; i++) {
-						pendingJoints.push(parser.getDependency('node', skinEntry.joints[i]));
 					}
 
-					return Promise.all(pendingJoints);
-				}).then(function (jointNodes) {
-					node.traverse(function (mesh) {
-						if (!mesh.isMesh) return;
-						var bones = [];
-						var boneInverses = [];
+					return Promise.all( pending );
 
-						for (var j = 0, jl = jointNodes.length; j < jl; j++) {
-							var jointNode = jointNodes[j];
+				} );
 
-							if (jointNode) {
-								bones.push(jointNode);
-								var mat = new THREE.Matrix4();
+			}
 
-								if (skinEntry.inverseBindMatrices !== undefined) {
-									mat.fromArray(skinEntry.inverseBindMatrices.array, j * 16);
-								}
+			return function loadScene( sceneIndex ) {
 
-								boneInverses.push(mat);
-							} else {
-								console.warn('THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[j]);
-							}
-						}
+				var json = this.json;
+				var extensions = this.extensions;
+				var sceneDef = this.json.scenes[ sceneIndex ];
+				var parser = this; // THREE.Loader returns THREE.Group, not Scene.
+				// See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172
 
-						mesh.bind(new THREE.Skeleton(bones, boneInverses), mesh.matrixWorld);
-					});
-					return node;
-				});
-			}).then(function (node) {
-				// build node hierachy
-				parentObject.add(node);
+				var scene = new THREE.Group();
+				if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name );
+				assignExtrasToUserData( scene, sceneDef );
+				if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef );
+				var nodeIds = sceneDef.nodes || [];
 				var pending = [];
 
-				if (nodeDef.children) {
-					var children = nodeDef.children;
+				for ( var i = 0, il = nodeIds.length; i < il; i ++ ) {
+
+					pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) );
 
-					for (var i = 0, il = children.length; i < il; i++) {
-						var child = children[i];
-						pending.push(buildNodeHierachy(child, node, json, parser));
-					}
 				}
 
-				return Promise.all(pending);
-			});
-		}
+				return Promise.all( pending ).then( function () {
 
-		return function loadScene(sceneIndex) {
-			var json = this.json;
-			var extensions = this.extensions;
-			var sceneDef = this.json.scenes[sceneIndex];
-			var parser = this; // THREE.Loader returns THREE.Group, not Scene.
-			// See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172
-
-			var scene = new THREE.Group();
-			if (sceneDef.name) scene.name = parser.createUniqueName(sceneDef.name);
-			assignExtrasToUserData(scene, sceneDef);
-			if (sceneDef.extensions) addUnknownExtensionsToUserData(extensions, scene, sceneDef);
-			var nodeIds = sceneDef.nodes || [];
-			var pending = [];
+					return scene;
 
-			for (var i = 0, il = nodeIds.length; i < il; i++) {
-				pending.push(buildNodeHierachy(nodeIds[i], scene, json, parser));
-			}
+				} );
+
+			};
+
+		}();
+
+		return GLTFLoader;
 
-			return Promise.all(pending).then(function () {
-				return scene;
-			});
-		};
 	}();
 
-	return GLTFLoader;
-}();
+	THREE.GLTFLoader = GLTFLoader;
 
-THREE.GLTFLoader = GLTFLoader;
 } )();

+ 96 - 95
examples/jsm/effects/AnaglyphEffect.js

@@ -12,156 +12,157 @@ import {
 	WebGLRenderTarget
 } from '../../../build/three.module.js';
 
-var AnaglyphEffect = function ( renderer, width, height ) {
+class AnaglyphEffect {
 
-	// Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4
+	constructor( renderer, width = 512, height = 512 ) {
 
-	this.colorMatrixLeft = new Matrix3().fromArray( [
-		0.456100, - 0.0400822, - 0.0152161,
-		0.500484, - 0.0378246, - 0.0205971,
-		0.176381, - 0.0157589, - 0.00546856
-	] );
+		// Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4
 
-	this.colorMatrixRight = new Matrix3().fromArray( [
-		- 0.0434706, 0.378476, - 0.0721527,
-		- 0.0879388, 0.73364, - 0.112961,
-		- 0.00155529, - 0.0184503, 1.2264
-	] );
+		this.colorMatrixLeft = new Matrix3().fromArray( [
+			0.456100, - 0.0400822, - 0.0152161,
+			0.500484, - 0.0378246, - 0.0205971,
+			0.176381, - 0.0157589, - 0.00546856
+		] );
 
-	var _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+		this.colorMatrixRight = new Matrix3().fromArray( [
+			- 0.0434706, 0.378476, - 0.0721527,
+			- 0.0879388, 0.73364, - 0.112961,
+			- 0.00155529, - 0.0184503, 1.2264
+		] );
 
-	var _scene = new Scene();
+		const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
 
-	var _stereo = new StereoCamera();
+		const _scene = new Scene();
 
-	var _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat };
+		const _stereo = new StereoCamera();
 
-	if ( width === undefined ) width = 512;
-	if ( height === undefined ) height = 512;
+		const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat };
 
-	var _renderTargetL = new WebGLRenderTarget( width, height, _params );
-	var _renderTargetR = new WebGLRenderTarget( width, height, _params );
+		const _renderTargetL = new WebGLRenderTarget( width, height, _params );
+		const _renderTargetR = new WebGLRenderTarget( width, height, _params );
 
-	var _material = new ShaderMaterial( {
+		const _material = new ShaderMaterial( {
 
-		uniforms: {
+			uniforms: {
 
-			'mapLeft': { value: _renderTargetL.texture },
-			'mapRight': { value: _renderTargetR.texture },
+				'mapLeft': { value: _renderTargetL.texture },
+				'mapRight': { value: _renderTargetR.texture },
 
-			'colorMatrixLeft': { value: this.colorMatrixLeft },
-			'colorMatrixRight': { value: this.colorMatrixRight }
+				'colorMatrixLeft': { value: this.colorMatrixLeft },
+				'colorMatrixRight': { value: this.colorMatrixRight }
 
-		},
+			},
 
-		vertexShader: [
+			vertexShader: [
 
-			'varying vec2 vUv;',
+				'varying vec2 vUv;',
 
-			'void main() {',
+				'void main() {',
 
-			'	vUv = vec2( uv.x, uv.y );',
-			'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
+				'	vUv = vec2( uv.x, uv.y );',
+				'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
 
-			'}'
+				'}'
 
-		].join( '\n' ),
+			].join( '\n' ),
 
-		fragmentShader: [
+			fragmentShader: [
 
-			'uniform sampler2D mapLeft;',
-			'uniform sampler2D mapRight;',
-			'varying vec2 vUv;',
+				'uniform sampler2D mapLeft;',
+				'uniform sampler2D mapRight;',
+				'varying vec2 vUv;',
 
-			'uniform mat3 colorMatrixLeft;',
-			'uniform mat3 colorMatrixRight;',
+				'uniform mat3 colorMatrixLeft;',
+				'uniform mat3 colorMatrixRight;',
 
-			// These functions implement sRGB linearization and gamma correction
+				// These functions implement sRGB linearization and gamma correction
 
-			'float lin( float c ) {',
-			'	return c <= 0.04045 ? c * 0.0773993808 :',
-			'			pow( c * 0.9478672986 + 0.0521327014, 2.4 );',
-			'}',
+				'float lin( float c ) {',
+				'	return c <= 0.04045 ? c * 0.0773993808 :',
+				'			pow( c * 0.9478672986 + 0.0521327014, 2.4 );',
+				'}',
 
-			'vec4 lin( vec4 c ) {',
-			'	return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );',
-			'}',
+				'vec4 lin( vec4 c ) {',
+				'	return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );',
+				'}',
 
-			'float dev( float c ) {',
-			'	return c <= 0.0031308 ? c * 12.92',
-			'			: pow( c, 0.41666 ) * 1.055 - 0.055;',
-			'}',
+				'float dev( float c ) {',
+				'	return c <= 0.0031308 ? c * 12.92',
+				'			: pow( c, 0.41666 ) * 1.055 - 0.055;',
+				'}',
 
 
-			'void main() {',
+				'void main() {',
 
-			'	vec2 uv = vUv;',
+				'	vec2 uv = vUv;',
 
-			'	vec4 colorL = lin( texture2D( mapLeft, uv ) );',
-			'	vec4 colorR = lin( texture2D( mapRight, uv ) );',
+				'	vec4 colorL = lin( texture2D( mapLeft, uv ) );',
+				'	vec4 colorR = lin( texture2D( mapRight, uv ) );',
 
-			'	vec3 color = clamp(',
-			'			colorMatrixLeft * colorL.rgb +',
-			'			colorMatrixRight * colorR.rgb, 0., 1. );',
+				'	vec3 color = clamp(',
+				'			colorMatrixLeft * colorL.rgb +',
+				'			colorMatrixRight * colorR.rgb, 0., 1. );',
 
-			'	gl_FragColor = vec4(',
-			'			dev( color.r ), dev( color.g ), dev( color.b ),',
-			'			max( colorL.a, colorR.a ) );',
+				'	gl_FragColor = vec4(',
+				'			dev( color.r ), dev( color.g ), dev( color.b ),',
+				'			max( colorL.a, colorR.a ) );',
 
-			'}'
+				'}'
 
-		].join( '\n' )
+			].join( '\n' )
 
-	} );
+		} );
 
-	var _mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material );
-	_scene.add( _mesh );
+		const _mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material );
+		_scene.add( _mesh );
 
-	this.setSize = function ( width, height ) {
+		this.setSize = function ( width, height ) {
 
-		renderer.setSize( width, height );
+			renderer.setSize( width, height );
 
-		var pixelRatio = renderer.getPixelRatio();
+			const pixelRatio = renderer.getPixelRatio();
 
-		_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
-		_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
+			_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
+			_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
 
-	};
+		};
 
-	this.render = function ( scene, camera ) {
+		this.render = function ( scene, camera ) {
 
-		var currentRenderTarget = renderer.getRenderTarget();
+			const currentRenderTarget = renderer.getRenderTarget();
 
-		scene.updateMatrixWorld();
+			scene.updateMatrixWorld();
 
-		if ( camera.parent === null ) camera.updateMatrixWorld();
+			if ( camera.parent === null ) camera.updateMatrixWorld();
 
-		_stereo.update( camera );
+			_stereo.update( camera );
 
-		renderer.setRenderTarget( _renderTargetL );
-		renderer.clear();
-		renderer.render( scene, _stereo.cameraL );
+			renderer.setRenderTarget( _renderTargetL );
+			renderer.clear();
+			renderer.render( scene, _stereo.cameraL );
 
-		renderer.setRenderTarget( _renderTargetR );
-		renderer.clear();
-		renderer.render( scene, _stereo.cameraR );
+			renderer.setRenderTarget( _renderTargetR );
+			renderer.clear();
+			renderer.render( scene, _stereo.cameraR );
 
-		renderer.setRenderTarget( null );
-		renderer.render( _scene, _camera );
+			renderer.setRenderTarget( null );
+			renderer.render( _scene, _camera );
 
-		renderer.setRenderTarget( currentRenderTarget );
+			renderer.setRenderTarget( currentRenderTarget );
 
-	};
+		};
 
-	this.dispose = function () {
+		this.dispose = function () {
 
-		if ( _renderTargetL ) _renderTargetL.dispose();
-		if ( _renderTargetR ) _renderTargetR.dispose();
-		if ( _mesh ) _mesh.geometry.dispose();
-		if ( _material ) _material.dispose();
+			if ( _renderTargetL ) _renderTargetL.dispose();
+			if ( _renderTargetR ) _renderTargetR.dispose();
+			if ( _mesh ) _mesh.geometry.dispose();
+			if ( _material ) _material.dispose();
 
-	};
+		};
 
-};
+	}
+
+}
 
 export { AnaglyphEffect };

+ 167 - 171
examples/jsm/effects/AsciiEffect.js

@@ -5,286 +5,282 @@
  * 16 April 2012 - @blurspline
  */
 
-var AsciiEffect = function ( renderer, charSet, options ) {
+class AsciiEffect {
 
-	// its fun to create one your own!
+	constructor( renderer, charSet = ' .:-=+*#%@', options = {} ) {
 
-	charSet = ( charSet === undefined ) ? ' .:-=+*#%@' : charSet;
+		// ' .,:;=|iI+hHOE#`$';
+		// darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/
+		// ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split('');
 
-	// ' .,:;=|iI+hHOE#`$';
-	// darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/
-	// ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split('');
+		// Some ASCII settings
 
-	if ( ! options ) options = {};
+		const bResolution = ! options[ 'resolution' ] ? 0.15 : options[ 'resolution' ]; // Higher for more details
+		const iScale = ! options[ 'scale' ] ? 1 : options[ 'scale' ];
+		const bColor = ! options[ 'color' ] ? false : options[ 'color' ]; // nice but slows down rendering!
+		const bAlpha = ! options[ 'alpha' ] ? false : options[ 'alpha' ]; // Transparency
+		const bBlock = ! options[ 'block' ] ? false : options[ 'block' ]; // blocked characters. like good O dos
+		const bInvert = ! options[ 'invert' ] ? false : options[ 'invert' ]; // black is white, white is black
 
-	// Some ASCII settings
+		const strResolution = 'low';
 
-	var bResolution = ! options[ 'resolution' ] ? 0.15 : options[ 'resolution' ]; // Higher for more details
-	var iScale = ! options[ 'scale' ] ? 1 : options[ 'scale' ];
-	var bColor = ! options[ 'color' ] ? false : options[ 'color' ]; // nice but slows down rendering!
-	var bAlpha = ! options[ 'alpha' ] ? false : options[ 'alpha' ]; // Transparency
-	var bBlock = ! options[ 'block' ] ? false : options[ 'block' ]; // blocked characters. like good O dos
-	var bInvert = ! options[ 'invert' ] ? false : options[ 'invert' ]; // black is white, white is black
+		let width, height;
 
-	var strResolution = 'low';
+		const domElement = document.createElement( 'div' );
+		domElement.style.cursor = 'default';
 
-	var width, height;
+		const oAscii = document.createElement( 'table' );
+		domElement.appendChild( oAscii );
 
-	var domElement = document.createElement( 'div' );
-	domElement.style.cursor = 'default';
+		let iWidth, iHeight;
+		let oImg;
 
-	var oAscii = document.createElement( 'table' );
-	domElement.appendChild( oAscii );
+		this.setSize = function ( w, h ) {
 
-	var iWidth, iHeight;
-	var oImg;
+			width = w;
+			height = h;
 
-	this.setSize = function ( w, h ) {
+			renderer.setSize( w, h );
 
-		width = w;
-		height = h;
+			initAsciiSize();
 
-		renderer.setSize( w, h );
+		};
 
-		initAsciiSize();
 
-	};
+		this.render = function ( scene, camera ) {
 
+			renderer.render( scene, camera );
+			asciifyImage( renderer, oAscii );
 
-	this.render = function ( scene, camera ) {
+		};
 
-		renderer.render( scene, camera );
-		asciifyImage( renderer, oAscii );
+		this.domElement = domElement;
 
-	};
 
-	this.domElement = domElement;
+		// Throw in ascii library from http://www.nihilogic.dk/labs/jsascii/jsascii.js
 
+		/*
+		* jsAscii 0.1
+		* Copyright (c) 2008 Jacob Seidelin, [email protected], http://blog.nihilogic.dk/
+		* MIT License [http://www.nihilogic.dk/licenses/mit-license.txt]
+		*/
 
-	// Throw in ascii library from http://www.nihilogic.dk/labs/jsascii/jsascii.js
+		function initAsciiSize() {
 
-	/*
-	* jsAscii 0.1
-	* Copyright (c) 2008 Jacob Seidelin, [email protected], http://blog.nihilogic.dk/
-	* MIT License [http://www.nihilogic.dk/licenses/mit-license.txt]
-	*/
+			iWidth = Math.round( width * fResolution );
+			iHeight = Math.round( height * fResolution );
 
-	function initAsciiSize() {
+			oCanvas.width = iWidth;
+			oCanvas.height = iHeight;
+			// oCanvas.style.display = "none";
+			// oCanvas.style.width = iWidth;
+			// oCanvas.style.height = iHeight;
 
-		iWidth = Math.round( width * fResolution );
-		iHeight = Math.round( height * fResolution );
+			oImg = renderer.domElement;
 
-		oCanvas.width = iWidth;
-		oCanvas.height = iHeight;
-		// oCanvas.style.display = "none";
-		// oCanvas.style.width = iWidth;
-		// oCanvas.style.height = iHeight;
+			if ( oImg.style.backgroundColor ) {
 
-		oImg = renderer.domElement;
+				oAscii.rows[ 0 ].cells[ 0 ].style.backgroundColor = oImg.style.backgroundColor;
+				oAscii.rows[ 0 ].cells[ 0 ].style.color = oImg.style.color;
 
-		if ( oImg.style.backgroundColor ) {
+			}
 
-			oAscii.rows[ 0 ].cells[ 0 ].style.backgroundColor = oImg.style.backgroundColor;
-			oAscii.rows[ 0 ].cells[ 0 ].style.color = oImg.style.color;
+			oAscii.cellSpacing = 0;
+			oAscii.cellPadding = 0;
+
+			const oStyle = oAscii.style;
+			oStyle.display = 'inline';
+			oStyle.width = Math.round( iWidth / fResolution * iScale ) + 'px';
+			oStyle.height = Math.round( iHeight / fResolution * iScale ) + 'px';
+			oStyle.whiteSpace = 'pre';
+			oStyle.margin = '0px';
+			oStyle.padding = '0px';
+			oStyle.letterSpacing = fLetterSpacing + 'px';
+			oStyle.fontFamily = strFont;
+			oStyle.fontSize = fFontSize + 'px';
+			oStyle.lineHeight = fLineHeight + 'px';
+			oStyle.textAlign = 'left';
+			oStyle.textDecoration = 'none';
 
 		}
 
-		oAscii.cellSpacing = 0;
-		oAscii.cellPadding = 0;
-
-		var oStyle = oAscii.style;
-		oStyle.display = 'inline';
-		oStyle.width = Math.round( iWidth / fResolution * iScale ) + 'px';
-		oStyle.height = Math.round( iHeight / fResolution * iScale ) + 'px';
-		oStyle.whiteSpace = 'pre';
-		oStyle.margin = '0px';
-		oStyle.padding = '0px';
-		oStyle.letterSpacing = fLetterSpacing + 'px';
-		oStyle.fontFamily = strFont;
-		oStyle.fontSize = fFontSize + 'px';
-		oStyle.lineHeight = fLineHeight + 'px';
-		oStyle.textAlign = 'left';
-		oStyle.textDecoration = 'none';
 
-	}
+		const aDefaultCharList = ( ' .,:;i1tfLCG08@' ).split( '' );
+		const aDefaultColorCharList = ( ' CGO08@' ).split( '' );
+		const strFont = 'courier new, monospace';
 
+		const oCanvasImg = renderer.domElement;
 
-	var aDefaultCharList = ( ' .,:;i1tfLCG08@' ).split( '' );
-	var aDefaultColorCharList = ( ' CGO08@' ).split( '' );
-	var strFont = 'courier new, monospace';
+		const oCanvas = document.createElement( 'canvas' );
+		if ( ! oCanvas.getContext ) {
 
-	var oCanvasImg = renderer.domElement;
+			return;
 
-	var oCanvas = document.createElement( 'canvas' );
-	if ( ! oCanvas.getContext ) {
+		}
 
-		return;
+		const oCtx = oCanvas.getContext( '2d' );
+		if ( ! oCtx.getImageData ) {
 
-	}
+			return;
 
-	var oCtx = oCanvas.getContext( '2d' );
-	if ( ! oCtx.getImageData ) {
+		}
 
-		return;
+		let aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList );
 
-	}
+		if ( charSet ) aCharList = charSet;
 
-	var aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList );
+		let fResolution = 0.5;
 
-	if ( charSet ) aCharList = charSet;
+		switch ( strResolution ) {
 
-	var fResolution = 0.5;
+			case 'low' : 	fResolution = 0.25; break;
+			case 'medium' : fResolution = 0.5; break;
+			case 'high' : 	fResolution = 1; break;
 
-	switch ( strResolution ) {
-
-		case 'low' : 	fResolution = 0.25; break;
-		case 'medium' : fResolution = 0.5; break;
-		case 'high' : 	fResolution = 1; break;
+		}
 
-	}
+		if ( bResolution ) fResolution = bResolution;
 
-	if ( bResolution ) fResolution = bResolution;
+		// Setup dom
 
-	// Setup dom
+		const fFontSize = ( 2 / fResolution ) * iScale;
+		const fLineHeight = ( 2 / fResolution ) * iScale;
 
-	var fFontSize = ( 2 / fResolution ) * iScale;
-	var fLineHeight = ( 2 / fResolution ) * iScale;
+		// adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width.
 
-	// adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width.
+		let fLetterSpacing = 0;
 
-	var fLetterSpacing = 0;
+		if ( strResolution == 'low' ) {
 
-	if ( strResolution == 'low' ) {
+			switch ( iScale ) {
 
-		switch ( iScale ) {
+				case 1 : fLetterSpacing = - 1; break;
+				case 2 :
+				case 3 : fLetterSpacing = - 2.1; break;
+				case 4 : fLetterSpacing = - 3.1; break;
+				case 5 : fLetterSpacing = - 4.15; break;
 
-			case 1 : fLetterSpacing = - 1; break;
-			case 2 :
-			case 3 : fLetterSpacing = - 2.1; break;
-			case 4 : fLetterSpacing = - 3.1; break;
-			case 5 : fLetterSpacing = - 4.15; break;
+			}
 
 		}
 
-	}
+		if ( strResolution == 'medium' ) {
 
-	if ( strResolution == 'medium' ) {
+			switch ( iScale ) {
 
-		switch ( iScale ) {
+				case 1 : fLetterSpacing = 0; break;
+				case 2 : fLetterSpacing = - 1; break;
+				case 3 : fLetterSpacing = - 1.04; break;
+				case 4 :
+				case 5 : fLetterSpacing = - 2.1; break;
 
-			case 1 : fLetterSpacing = 0; break;
-			case 2 : fLetterSpacing = - 1; break;
-			case 3 : fLetterSpacing = - 1.04; break;
-			case 4 :
-			case 5 : fLetterSpacing = - 2.1; break;
+			}
 
 		}
 
-	}
+		if ( strResolution == 'high' ) {
 
-	if ( strResolution == 'high' ) {
+			switch ( iScale ) {
 
-		switch ( iScale ) {
+				case 1 :
+				case 2 : fLetterSpacing = 0; break;
+				case 3 :
+				case 4 :
+				case 5 : fLetterSpacing = - 1; break;
 
-			case 1 :
-			case 2 : fLetterSpacing = 0; break;
-			case 3 :
-			case 4 :
-			case 5 : fLetterSpacing = - 1; break;
+			}
 
 		}
 
-	}
 
+		// can't get a span or div to flow like an img element, but a table works?
 
-	// can't get a span or div to flow like an img element, but a table works?
 
+		// convert img element to ascii
 
-	// convert img element to ascii
+		function asciifyImage( canvasRenderer, oAscii ) {
 
-	function asciifyImage( canvasRenderer, oAscii ) {
+			oCtx.clearRect( 0, 0, iWidth, iHeight );
+			oCtx.drawImage( oCanvasImg, 0, 0, iWidth, iHeight );
+			const oImgData = oCtx.getImageData( 0, 0, iWidth, iHeight ).data;
 
-		oCtx.clearRect( 0, 0, iWidth, iHeight );
-		oCtx.drawImage( oCanvasImg, 0, 0, iWidth, iHeight );
-		var oImgData = oCtx.getImageData( 0, 0, iWidth, iHeight ).data;
+			// Coloring loop starts now
+			let strChars = '';
 
-		// Coloring loop starts now
-		var strChars = '';
+			// console.time('rendering');
 
-		// console.time('rendering');
+			for ( let y = 0; y < iHeight; y += 2 ) {
 
-		for ( var y = 0; y < iHeight; y += 2 ) {
+				for ( let x = 0; x < iWidth; x ++ ) {
 
-			for ( var x = 0; x < iWidth; x ++ ) {
+					const iOffset = ( y * iWidth + x ) * 4;
 
-				var iOffset = ( y * iWidth + x ) * 4;
+					const iRed = oImgData[ iOffset ];
+					const iGreen = oImgData[ iOffset + 1 ];
+					const iBlue = oImgData[ iOffset + 2 ];
+					const iAlpha = oImgData[ iOffset + 3 ];
+					let iCharIdx;
 
-				var iRed = oImgData[ iOffset ];
-				var iGreen = oImgData[ iOffset + 1 ];
-				var iBlue = oImgData[ iOffset + 2 ];
-				var iAlpha = oImgData[ iOffset + 3 ];
-				var iCharIdx;
+					let fBrightness;
 
-				var fBrightness;
+					fBrightness = ( 0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue ) / 255;
+					// fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255;
 
-				fBrightness = ( 0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue ) / 255;
-				// fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255;
+					if ( iAlpha == 0 ) {
 
-				if ( iAlpha == 0 ) {
+						// should calculate alpha instead, but quick hack :)
+						//fBrightness *= (iAlpha / 255);
+						fBrightness = 1;
 
-					// should calculate alpha instead, but quick hack :)
-					//fBrightness *= (iAlpha / 255);
-					fBrightness = 1;
+					}
 
-				}
+					iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) );
 
-				iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) );
+					if ( bInvert ) {
 
-				if ( bInvert ) {
+						iCharIdx = aCharList.length - iCharIdx - 1;
 
-					iCharIdx = aCharList.length - iCharIdx - 1;
+					}
 
-				}
+					// good for debugging
+					//fBrightness = Math.floor(fBrightness * 10);
+					//strThisChar = fBrightness;
 
-				// good for debugging
-				//fBrightness = Math.floor(fBrightness * 10);
-				//strThisChar = fBrightness;
+					let strThisChar = aCharList[ iCharIdx ];
 
-				var strThisChar = aCharList[ iCharIdx ];
+					if ( strThisChar === undefined || strThisChar == ' ' )
+						strThisChar = '&nbsp;';
 
-				if ( strThisChar === undefined || strThisChar == ' ' )
-					strThisChar = '&nbsp;';
+					if ( bColor ) {
 
-				if ( bColor ) {
+						strChars += '<span style=\''
+							+ 'color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');'
+							+ ( bBlock ? 'background-color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' : '' )
+							+ ( bAlpha ? 'opacity:' + ( iAlpha / 255 ) + ';' : '' )
+							+ '\'>' + strThisChar + '</span>';
 
-					strChars += '<span style=\''
-						+ 'color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');'
-						+ ( bBlock ? 'background-color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' : '' )
-						+ ( bAlpha ? 'opacity:' + ( iAlpha / 255 ) + ';' : '' )
-						+ '\'>' + strThisChar + '</span>';
+					} else {
 
-				} else {
+						strChars += strThisChar;
 
-					strChars += strThisChar;
+					}
 
 				}
 
-			}
+				strChars += '<br/>';
 
-			strChars += '<br/>';
+			}
 
-		}
+			oAscii.innerHTML = '<tr><td>' + strChars + '</td></tr>';
 
-		oAscii.innerHTML = '<tr><td>' + strChars + '</td></tr>';
+			// console.timeEnd('rendering');
 
-		// console.timeEnd('rendering');
+			// return oAscii;
 
-		// return oAscii;
+		}
 
 	}
 
-	// end modified asciifyImage block
-
-};
+}
 
 export { AsciiEffect };

+ 311 - 309
examples/jsm/effects/OutlineEffect.js

@@ -13,7 +13,7 @@ import {
  *
  * 1. Traditional
  *
- * var effect = new OutlineEffect( renderer );
+ * const effect = new OutlineEffect( renderer );
  *
  * function render() {
  *
@@ -23,8 +23,8 @@ import {
  *
  * 2. VR compatible
  *
- * var effect = new OutlineEffect( renderer );
- * var renderingOutline = false;
+ * const effect = new OutlineEffect( renderer );
+ * let renderingOutline = false;
  *
  * scene.onAfterRender = function () {
  *
@@ -62,514 +62,516 @@ import {
  * };
  */
 
-var OutlineEffect = function ( renderer, parameters ) {
+class OutlineEffect {
 
-	parameters = parameters || {};
+	constructor( renderer, parameters = {} ) {
 
-	this.enabled = true;
+		this.enabled = true;
 
-	var defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003;
-	var defaultColor = new Color().fromArray( parameters.defaultColor !== undefined ? parameters.defaultColor : [ 0, 0, 0 ] );
-	var defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0;
-	var defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false;
+		const defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003;
+		const defaultColor = new Color().fromArray( parameters.defaultColor !== undefined ? parameters.defaultColor : [ 0, 0, 0 ] );
+		const defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0;
+		const defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false;
 
-	// object.material.uuid -> outlineMaterial or
-	// object.material[ n ].uuid -> outlineMaterial
-	// save at the outline material creation and release
-	// if it's unused removeThresholdCount frames
-	// unless keepAlive is true.
-	var cache = {};
+		// object.material.uuid -> outlineMaterial or
+		// object.material[ n ].uuid -> outlineMaterial
+		// save at the outline material creation and release
+		// if it's unused removeThresholdCount frames
+		// unless keepAlive is true.
+		const cache = {};
 
-	var removeThresholdCount = 60;
+		const removeThresholdCount = 60;
 
-	// outlineMaterial.uuid -> object.material or
-	// outlineMaterial.uuid -> object.material[ n ]
-	// save before render and release after render.
-	var originalMaterials = {};
+		// outlineMaterial.uuid -> object.material or
+		// outlineMaterial.uuid -> object.material[ n ]
+		// save before render and release after render.
+		const originalMaterials = {};
 
-	// object.uuid -> originalOnBeforeRender
-	// save before render and release after render.
-	var originalOnBeforeRenders = {};
+		// object.uuid -> originalOnBeforeRender
+		// save before render and release after render.
+		const originalOnBeforeRenders = {};
 
-	//this.cache = cache;  // for debug
+		//this.cache = cache;  // for debug
 
-	var uniformsOutline = {
-		outlineThickness: { value: defaultThickness },
-		outlineColor: { value: defaultColor },
-		outlineAlpha: { value: defaultAlpha }
-	};
+		const uniformsOutline = {
+			outlineThickness: { value: defaultThickness },
+			outlineColor: { value: defaultColor },
+			outlineAlpha: { value: defaultAlpha }
+		};
 
-	var vertexShader = [
-		'#include <common>',
-		'#include <uv_pars_vertex>',
-		'#include <displacementmap_pars_vertex>',
-		'#include <fog_pars_vertex>',
-		'#include <morphtarget_pars_vertex>',
-		'#include <skinning_pars_vertex>',
-		'#include <logdepthbuf_pars_vertex>',
-		'#include <clipping_planes_pars_vertex>',
+		const vertexShader = [
+			'#include <common>',
+			'#include <uv_pars_vertex>',
+			'#include <displacementmap_pars_vertex>',
+			'#include <fog_pars_vertex>',
+			'#include <morphtarget_pars_vertex>',
+			'#include <skinning_pars_vertex>',
+			'#include <logdepthbuf_pars_vertex>',
+			'#include <clipping_planes_pars_vertex>',
 
-		'uniform float outlineThickness;',
+			'uniform float outlineThickness;',
 
-		'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {',
-		'	float thickness = outlineThickness;',
-		'	const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex
-		'	vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );',
-		// NOTE: subtract pos2 from pos because BackSide objectNormal is negative
-		'	vec4 norm = normalize( pos - pos2 );',
-		'	return pos + norm * thickness * pos.w * ratio;',
-		'}',
+			'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {',
+			'	float thickness = outlineThickness;',
+			'	const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex
+			'	vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );',
+			// NOTE: subtract pos2 from pos because BackSide objectNormal is negative
+			'	vec4 norm = normalize( pos - pos2 );',
+			'	return pos + norm * thickness * pos.w * ratio;',
+			'}',
 
-		'void main() {',
+			'void main() {',
 
-		'	#include <uv_vertex>',
+			'	#include <uv_vertex>',
 
-		'	#include <beginnormal_vertex>',
-		'	#include <morphnormal_vertex>',
-		'	#include <skinbase_vertex>',
-		'	#include <skinnormal_vertex>',
+			'	#include <beginnormal_vertex>',
+			'	#include <morphnormal_vertex>',
+			'	#include <skinbase_vertex>',
+			'	#include <skinnormal_vertex>',
 
-		'	#include <begin_vertex>',
-		'	#include <morphtarget_vertex>',
-		'	#include <skinning_vertex>',
-		'	#include <displacementmap_vertex>',
-		'	#include <project_vertex>',
+			'	#include <begin_vertex>',
+			'	#include <morphtarget_vertex>',
+			'	#include <skinning_vertex>',
+			'	#include <displacementmap_vertex>',
+			'	#include <project_vertex>',
 
-		'	vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with BackSide
+			'	vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with BackSide
 
-		'	gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );',
+			'	gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );',
 
-		'	#include <logdepthbuf_vertex>',
-		'	#include <clipping_planes_vertex>',
-		'	#include <fog_vertex>',
+			'	#include <logdepthbuf_vertex>',
+			'	#include <clipping_planes_vertex>',
+			'	#include <fog_vertex>',
 
-		'}',
+			'}',
 
-	].join( '\n' );
+		].join( '\n' );
 
-	var fragmentShader = [
+		const fragmentShader = [
 
-		'#include <common>',
-		'#include <fog_pars_fragment>',
-		'#include <logdepthbuf_pars_fragment>',
-		'#include <clipping_planes_pars_fragment>',
+			'#include <common>',
+			'#include <fog_pars_fragment>',
+			'#include <logdepthbuf_pars_fragment>',
+			'#include <clipping_planes_pars_fragment>',
 
-		'uniform vec3 outlineColor;',
-		'uniform float outlineAlpha;',
+			'uniform vec3 outlineColor;',
+			'uniform float outlineAlpha;',
 
-		'void main() {',
+			'void main() {',
 
-		'	#include <clipping_planes_fragment>',
-		'	#include <logdepthbuf_fragment>',
+			'	#include <clipping_planes_fragment>',
+			'	#include <logdepthbuf_fragment>',
 
-		'	gl_FragColor = vec4( outlineColor, outlineAlpha );',
+			'	gl_FragColor = vec4( outlineColor, outlineAlpha );',
 
-		'	#include <tonemapping_fragment>',
-		'	#include <encodings_fragment>',
-		'	#include <fog_fragment>',
-		'	#include <premultiplied_alpha_fragment>',
+			'	#include <tonemapping_fragment>',
+			'	#include <encodings_fragment>',
+			'	#include <fog_fragment>',
+			'	#include <premultiplied_alpha_fragment>',
 
-		'}'
+			'}'
 
-	].join( '\n' );
+		].join( '\n' );
 
-	function createMaterial() {
+		function createMaterial() {
 
-		return new ShaderMaterial( {
-			type: 'OutlineEffect',
-			uniforms: UniformsUtils.merge( [
-				UniformsLib[ 'fog' ],
-				UniformsLib[ 'displacementmap' ],
-				uniformsOutline
-			] ),
-			vertexShader: vertexShader,
-			fragmentShader: fragmentShader,
-			side: BackSide
-		} );
+			return new ShaderMaterial( {
+				type: 'OutlineEffect',
+				uniforms: UniformsUtils.merge( [
+					UniformsLib[ 'fog' ],
+					UniformsLib[ 'displacementmap' ],
+					uniformsOutline
+				] ),
+				vertexShader: vertexShader,
+				fragmentShader: fragmentShader,
+				side: BackSide
+			} );
 
-	}
+		}
 
-	function getOutlineMaterialFromCache( originalMaterial ) {
+		function getOutlineMaterialFromCache( originalMaterial ) {
 
-		var data = cache[ originalMaterial.uuid ];
+			let data = cache[ originalMaterial.uuid ];
 
-		if ( data === undefined ) {
+			if ( data === undefined ) {
 
-			data = {
-				material: createMaterial(),
-				used: true,
-				keepAlive: defaultKeepAlive,
-				count: 0
-			};
+				data = {
+					material: createMaterial(),
+					used: true,
+					keepAlive: defaultKeepAlive,
+					count: 0
+				};
 
-			cache[ originalMaterial.uuid ] = data;
+				cache[ originalMaterial.uuid ] = data;
 
-		}
+			}
 
-		data.used = true;
+			data.used = true;
 
-		return data.material;
+			return data.material;
 
-	}
+		}
 
-	function getOutlineMaterial( originalMaterial ) {
+		function getOutlineMaterial( originalMaterial ) {
 
-		var outlineMaterial = getOutlineMaterialFromCache( originalMaterial );
+			const outlineMaterial = getOutlineMaterialFromCache( originalMaterial );
 
-		originalMaterials[ outlineMaterial.uuid ] = originalMaterial;
+			originalMaterials[ outlineMaterial.uuid ] = originalMaterial;
 
-		updateOutlineMaterial( outlineMaterial, originalMaterial );
+			updateOutlineMaterial( outlineMaterial, originalMaterial );
 
-		return outlineMaterial;
+			return outlineMaterial;
 
-	}
+		}
 
-	function isCompatible( object ) {
+		function isCompatible( object ) {
 
-		var geometry = object.geometry;
-		var hasNormals = false;
+			const geometry = object.geometry;
+			let hasNormals = false;
 
-		if ( object.geometry !== undefined ) {
+			if ( object.geometry !== undefined ) {
 
-			if ( geometry.isBufferGeometry ) {
+				if ( geometry.isBufferGeometry ) {
 
-				hasNormals = geometry.attributes.normal !== undefined;
+					hasNormals = geometry.attributes.normal !== undefined;
 
-			} else {
+				} else {
+
+					hasNormals = true; // the renderer always produces a normal attribute for Geometry
 
-				hasNormals = true; // the renderer always produces a normal attribute for Geometry
+				}
 
 			}
 
+			return ( object.isMesh === true && object.material !== undefined && hasNormals === true );
+
 		}
 
-		return ( object.isMesh === true && object.material !== undefined && hasNormals === true );
+		function setOutlineMaterial( object ) {
 
-	}
+			if ( isCompatible( object ) === false ) return;
 
-	function setOutlineMaterial( object ) {
+			if ( Array.isArray( object.material ) ) {
 
-		if ( isCompatible( object ) === false ) return;
+				for ( let i = 0, il = object.material.length; i < il; i ++ ) {
 
-		if ( Array.isArray( object.material ) ) {
+					object.material[ i ] = getOutlineMaterial( object.material[ i ] );
 
-			for ( var i = 0, il = object.material.length; i < il; i ++ ) {
+				}
 
-				object.material[ i ] = getOutlineMaterial( object.material[ i ] );
+			} else {
 
-			}
+				object.material = getOutlineMaterial( object.material );
 
-		} else {
+			}
 
-			object.material = getOutlineMaterial( object.material );
+			originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender;
+			object.onBeforeRender = onBeforeRender;
 
 		}
 
-		originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender;
-		object.onBeforeRender = onBeforeRender;
+		function restoreOriginalMaterial( object ) {
 
-	}
+			if ( isCompatible( object ) === false ) return;
 
-	function restoreOriginalMaterial( object ) {
+			if ( Array.isArray( object.material ) ) {
 
-		if ( isCompatible( object ) === false ) return;
+				for ( let i = 0, il = object.material.length; i < il; i ++ ) {
 
-		if ( Array.isArray( object.material ) ) {
+					object.material[ i ] = originalMaterials[ object.material[ i ].uuid ];
 
-			for ( var i = 0, il = object.material.length; i < il; i ++ ) {
+				}
 
-				object.material[ i ] = originalMaterials[ object.material[ i ].uuid ];
+			} else {
 
-			}
+				object.material = originalMaterials[ object.material.uuid ];
 
-		} else {
+			}
 
-			object.material = originalMaterials[ object.material.uuid ];
+			object.onBeforeRender = originalOnBeforeRenders[ object.uuid ];
 
 		}
 
-		object.onBeforeRender = originalOnBeforeRenders[ object.uuid ];
+		function onBeforeRender( renderer, scene, camera, geometry, material ) {
 
-	}
-
-	function onBeforeRender( renderer, scene, camera, geometry, material ) {
+			const originalMaterial = originalMaterials[ material.uuid ];
 
-		var originalMaterial = originalMaterials[ material.uuid ];
+			// just in case
+			if ( originalMaterial === undefined ) return;
 
-		// just in case
-		if ( originalMaterial === undefined ) return;
+			updateUniforms( material, originalMaterial );
 
-		updateUniforms( material, originalMaterial );
+		}
 
-	}
+		function updateUniforms( material, originalMaterial ) {
 
-	function updateUniforms( material, originalMaterial ) {
+			const outlineParameters = originalMaterial.userData.outlineParameters;
 
-		var outlineParameters = originalMaterial.userData.outlineParameters;
+			material.uniforms.outlineAlpha.value = originalMaterial.opacity;
 
-		material.uniforms.outlineAlpha.value = originalMaterial.opacity;
+			if ( outlineParameters !== undefined ) {
 
-		if ( outlineParameters !== undefined ) {
+				if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness;
+				if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.fromArray( outlineParameters.color );
+				if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha;
 
-			if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness;
-			if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.fromArray( outlineParameters.color );
-			if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha;
+			}
 
-		}
+			if ( originalMaterial.displacementMap ) {
 
-		if ( originalMaterial.displacementMap ) {
+				material.uniforms.displacementMap.value = originalMaterial.displacementMap;
+				material.uniforms.displacementScale.value = originalMaterial.displacementScale;
+				material.uniforms.displacementBias.value = originalMaterial.displacementBias;
 
-			material.uniforms.displacementMap.value = originalMaterial.displacementMap;
-			material.uniforms.displacementScale.value = originalMaterial.displacementScale;
-			material.uniforms.displacementBias.value = originalMaterial.displacementBias;
+			}
 
 		}
 
-	}
+		function updateOutlineMaterial( material, originalMaterial ) {
 
-	function updateOutlineMaterial( material, originalMaterial ) {
+			if ( material.name === 'invisible' ) return;
 
-		if ( material.name === 'invisible' ) return;
+			const outlineParameters = originalMaterial.userData.outlineParameters;
 
-		var outlineParameters = originalMaterial.userData.outlineParameters;
+			material.skinning = originalMaterial.skinning;
+			material.morphTargets = originalMaterial.morphTargets;
+			material.morphNormals = originalMaterial.morphNormals;
+			material.fog = originalMaterial.fog;
+			material.toneMapped = originalMaterial.toneMapped;
+			material.premultipliedAlpha = originalMaterial.premultipliedAlpha;
+			material.displacementMap = originalMaterial.displacementMap;
 
-		material.skinning = originalMaterial.skinning;
-		material.morphTargets = originalMaterial.morphTargets;
-		material.morphNormals = originalMaterial.morphNormals;
-		material.fog = originalMaterial.fog;
-		material.toneMapped = originalMaterial.toneMapped;
-		material.premultipliedAlpha = originalMaterial.premultipliedAlpha;
-		material.displacementMap = originalMaterial.displacementMap;
+			if ( outlineParameters !== undefined ) {
 
-		if ( outlineParameters !== undefined ) {
+				if ( originalMaterial.visible === false ) {
 
-			if ( originalMaterial.visible === false ) {
+					material.visible = false;
 
-				material.visible = false;
+				} else {
 
-			} else {
+					material.visible = ( outlineParameters.visible !== undefined ) ? outlineParameters.visible : true;
 
-				material.visible = ( outlineParameters.visible !== undefined ) ? outlineParameters.visible : true;
+				}
 
-			}
+				material.transparent = ( outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ) ? true : originalMaterial.transparent;
 
-			material.transparent = ( outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ) ? true : originalMaterial.transparent;
+				if ( outlineParameters.keepAlive !== undefined ) cache[ originalMaterial.uuid ].keepAlive = outlineParameters.keepAlive;
 
-			if ( outlineParameters.keepAlive !== undefined ) cache[ originalMaterial.uuid ].keepAlive = outlineParameters.keepAlive;
+			} else {
 
-		} else {
+				material.transparent = originalMaterial.transparent;
+				material.visible = originalMaterial.visible;
 
-			material.transparent = originalMaterial.transparent;
-			material.visible = originalMaterial.visible;
+			}
 
-		}
+			if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false;
 
-		if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false;
+			if ( originalMaterial.clippingPlanes ) {
 
-		if ( originalMaterial.clippingPlanes ) {
+				material.clipping = true;
 
-			material.clipping = true;
+				material.clippingPlanes = originalMaterial.clippingPlanes;
+				material.clipIntersection = originalMaterial.clipIntersection;
+				material.clipShadows = originalMaterial.clipShadows;
 
-			material.clippingPlanes = originalMaterial.clippingPlanes;
-			material.clipIntersection = originalMaterial.clipIntersection;
-			material.clipShadows = originalMaterial.clipShadows;
+			}
+
+			material.version = originalMaterial.version; // update outline material if necessary
 
 		}
 
-		material.version = originalMaterial.version; // update outline material if necessary
+		function cleanupCache() {
 
-	}
+			let keys;
 
-	function cleanupCache() {
+			// clear originialMaterials
+			keys = Object.keys( originalMaterials );
 
-		var keys;
+			for ( let i = 0, il = keys.length; i < il; i ++ ) {
 
-		// clear originialMaterials
-		keys = Object.keys( originalMaterials );
+				originalMaterials[ keys[ i ] ] = undefined;
 
-		for ( var i = 0, il = keys.length; i < il; i ++ ) {
+			}
 
-			originalMaterials[ keys[ i ] ] = undefined;
+			// clear originalOnBeforeRenders
+			keys = Object.keys( originalOnBeforeRenders );
 
-		}
+			for ( let i = 0, il = keys.length; i < il; i ++ ) {
 
-		// clear originalOnBeforeRenders
-		keys = Object.keys( originalOnBeforeRenders );
+				originalOnBeforeRenders[ keys[ i ] ] = undefined;
 
-		for ( var i = 0, il = keys.length; i < il; i ++ ) {
+			}
 
-			originalOnBeforeRenders[ keys[ i ] ] = undefined;
+			// remove unused outlineMaterial from cache
+			keys = Object.keys( cache );
 
-		}
+			for ( let i = 0, il = keys.length; i < il; i ++ ) {
 
-		// remove unused outlineMaterial from cache
-		keys = Object.keys( cache );
+				const key = keys[ i ];
 
-		for ( var i = 0, il = keys.length; i < il; i ++ ) {
+				if ( cache[ key ].used === false ) {
 
-			var key = keys[ i ];
+					cache[ key ].count ++;
 
-			if ( cache[ key ].used === false ) {
+					if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) {
 
-				cache[ key ].count ++;
+						delete cache[ key ];
 
-				if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) {
+					}
 
-					delete cache[ key ];
+				} else {
 
-				}
+					cache[ key ].used = false;
+					cache[ key ].count = 0;
 
-			} else {
-
-				cache[ key ].used = false;
-				cache[ key ].count = 0;
+				}
 
 			}
 
 		}
 
-	}
+		this.render = function ( scene, camera ) {
 
-	this.render = function ( scene, camera ) {
+			let renderTarget;
+			let forceClear = false;
 
-		var renderTarget;
-		var forceClear = false;
+			if ( arguments[ 2 ] !== undefined ) {
 
-		if ( arguments[ 2 ] !== undefined ) {
+				console.warn( 'THREE.OutlineEffect.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' );
+				renderTarget = arguments[ 2 ];
 
-			console.warn( 'THREE.OutlineEffect.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' );
-			renderTarget = arguments[ 2 ];
+			}
 
-		}
+			if ( arguments[ 3 ] !== undefined ) {
 
-		if ( arguments[ 3 ] !== undefined ) {
+				console.warn( 'THREE.OutlineEffect.render(): the forceClear argument has been removed. Use .clear() instead.' );
+				forceClear = arguments[ 3 ];
 
-			console.warn( 'THREE.OutlineEffect.render(): the forceClear argument has been removed. Use .clear() instead.' );
-			forceClear = arguments[ 3 ];
+			}
 
-		}
+			if ( renderTarget !== undefined ) renderer.setRenderTarget( renderTarget );
 
-		if ( renderTarget !== undefined ) renderer.setRenderTarget( renderTarget );
+			if ( forceClear ) renderer.clear();
 
-		if ( forceClear ) renderer.clear();
+			if ( this.enabled === false ) {
 
-		if ( this.enabled === false ) {
+				renderer.render( scene, camera );
+				return;
 
-			renderer.render( scene, camera );
-			return;
+			}
 
-		}
+			const currentAutoClear = renderer.autoClear;
+			renderer.autoClear = this.autoClear;
 
-		var currentAutoClear = renderer.autoClear;
-		renderer.autoClear = this.autoClear;
+			renderer.render( scene, camera );
 
-		renderer.render( scene, camera );
+			renderer.autoClear = currentAutoClear;
 
-		renderer.autoClear = currentAutoClear;
+			this.renderOutline( scene, camera );
 
-		this.renderOutline( scene, camera );
+		};
 
-	};
+		this.renderOutline = function ( scene, camera ) {
 
-	this.renderOutline = function ( scene, camera ) {
+			const currentAutoClear = renderer.autoClear;
+			const currentSceneAutoUpdate = scene.autoUpdate;
+			const currentSceneBackground = scene.background;
+			const currentShadowMapEnabled = renderer.shadowMap.enabled;
 
-		var currentAutoClear = renderer.autoClear;
-		var currentSceneAutoUpdate = scene.autoUpdate;
-		var currentSceneBackground = scene.background;
-		var currentShadowMapEnabled = renderer.shadowMap.enabled;
+			scene.autoUpdate = false;
+			scene.background = null;
+			renderer.autoClear = false;
+			renderer.shadowMap.enabled = false;
 
-		scene.autoUpdate = false;
-		scene.background = null;
-		renderer.autoClear = false;
-		renderer.shadowMap.enabled = false;
+			scene.traverse( setOutlineMaterial );
 
-		scene.traverse( setOutlineMaterial );
+			renderer.render( scene, camera );
 
-		renderer.render( scene, camera );
+			scene.traverse( restoreOriginalMaterial );
 
-		scene.traverse( restoreOriginalMaterial );
+			cleanupCache();
 
-		cleanupCache();
+			scene.autoUpdate = currentSceneAutoUpdate;
+			scene.background = currentSceneBackground;
+			renderer.autoClear = currentAutoClear;
+			renderer.shadowMap.enabled = currentShadowMapEnabled;
 
-		scene.autoUpdate = currentSceneAutoUpdate;
-		scene.background = currentSceneBackground;
-		renderer.autoClear = currentAutoClear;
-		renderer.shadowMap.enabled = currentShadowMapEnabled;
+		};
 
-	};
+		/*
+		 * See #9918
+		 *
+		 * The following property copies and wrapper methods enable
+		 * OutlineEffect to be called from other *Effect, like
+		 *
+		 * effect = new StereoEffect( new OutlineEffect( renderer ) );
+		 *
+		 * function render () {
+		 *
+	 	 * 	effect.render( scene, camera );
+		 *
+		 * }
+		 */
+		this.autoClear = renderer.autoClear;
+		this.domElement = renderer.domElement;
+		this.shadowMap = renderer.shadowMap;
 
-	/*
-	 * See #9918
-	 *
-	 * The following property copies and wrapper methods enable
-	 * OutlineEffect to be called from other *Effect, like
-	 *
-	 * effect = new StereoEffect( new OutlineEffect( renderer ) );
-	 *
-	 * function render () {
-	 *
- 	 * 	effect.render( scene, camera );
-	 *
-	 * }
-	 */
-	this.autoClear = renderer.autoClear;
-	this.domElement = renderer.domElement;
-	this.shadowMap = renderer.shadowMap;
+		this.clear = function ( color, depth, stencil ) {
 
-	this.clear = function ( color, depth, stencil ) {
+			renderer.clear( color, depth, stencil );
 
-		renderer.clear( color, depth, stencil );
+		};
 
-	};
+		this.getPixelRatio = function () {
 
-	this.getPixelRatio = function () {
+			return renderer.getPixelRatio();
 
-		return renderer.getPixelRatio();
+		};
 
-	};
+		this.setPixelRatio = function ( value ) {
 
-	this.setPixelRatio = function ( value ) {
+			renderer.setPixelRatio( value );
 
-		renderer.setPixelRatio( value );
+		};
 
-	};
+		this.getSize = function ( target ) {
 
-	this.getSize = function ( target ) {
+			return renderer.getSize( target );
 
-		return renderer.getSize( target );
+		};
 
-	};
+		this.setSize = function ( width, height, updateStyle ) {
 
-	this.setSize = function ( width, height, updateStyle ) {
+			renderer.setSize( width, height, updateStyle );
 
-		renderer.setSize( width, height, updateStyle );
+		};
 
-	};
+		this.setViewport = function ( x, y, width, height ) {
 
-	this.setViewport = function ( x, y, width, height ) {
+			renderer.setViewport( x, y, width, height );
 
-		renderer.setViewport( x, y, width, height );
+		};
 
-	};
+		this.setScissor = function ( x, y, width, height ) {
 
-	this.setScissor = function ( x, y, width, height ) {
+			renderer.setScissor( x, y, width, height );
 
-		renderer.setScissor( x, y, width, height );
+		};
 
-	};
+		this.setScissorTest = function ( boolean ) {
 
-	this.setScissorTest = function ( boolean ) {
+			renderer.setScissorTest( boolean );
 
-		renderer.setScissorTest( boolean );
+		};
 
-	};
+		this.setRenderTarget = function ( renderTarget ) {
 
-	this.setRenderTarget = function ( renderTarget ) {
+			renderer.setRenderTarget( renderTarget );
 
-		renderer.setRenderTarget( renderTarget );
+		};
 
-	};
+	}
 
-};
+}
 
 export { OutlineEffect };

+ 59 - 55
examples/jsm/effects/ParallaxBarrierEffect.js

@@ -11,102 +11,106 @@ import {
 	WebGLRenderTarget
 } from '../../../build/three.module.js';
 
-var ParallaxBarrierEffect = function ( renderer ) {
+class ParallaxBarrierEffect {
 
-	var _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+	constructor( renderer ) {
 
-	var _scene = new Scene();
+		const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
 
-	var _stereo = new StereoCamera();
+		const _scene = new Scene();
 
-	var _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat };
+		const _stereo = new StereoCamera();
 
-	var _renderTargetL = new WebGLRenderTarget( 512, 512, _params );
-	var _renderTargetR = new WebGLRenderTarget( 512, 512, _params );
+		const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat };
 
-	var _material = new ShaderMaterial( {
+		const _renderTargetL = new WebGLRenderTarget( 512, 512, _params );
+		const _renderTargetR = new WebGLRenderTarget( 512, 512, _params );
 
-		uniforms: {
+		const _material = new ShaderMaterial( {
 
-			'mapLeft': { value: _renderTargetL.texture },
-			'mapRight': { value: _renderTargetR.texture }
+			uniforms: {
 
-		},
+				'mapLeft': { value: _renderTargetL.texture },
+				'mapRight': { value: _renderTargetR.texture }
 
-		vertexShader: [
+			},
 
-			'varying vec2 vUv;',
+			vertexShader: [
 
-			'void main() {',
+				'varying vec2 vUv;',
 
-			'	vUv = vec2( uv.x, uv.y );',
-			'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
+				'void main() {',
 
-			'}'
+				'	vUv = vec2( uv.x, uv.y );',
+				'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
 
-		].join( '\n' ),
+				'}'
 
-		fragmentShader: [
+			].join( '\n' ),
 
-			'uniform sampler2D mapLeft;',
-			'uniform sampler2D mapRight;',
-			'varying vec2 vUv;',
+			fragmentShader: [
 
-			'void main() {',
+				'uniform sampler2D mapLeft;',
+				'uniform sampler2D mapRight;',
+				'varying vec2 vUv;',
 
-			'	vec2 uv = vUv;',
+				'void main() {',
 
-			'	if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {',
+				'	vec2 uv = vUv;',
 
-			'		gl_FragColor = texture2D( mapLeft, uv );',
+				'	if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {',
 
-			'	} else {',
+				'		gl_FragColor = texture2D( mapLeft, uv );',
 
-			'		gl_FragColor = texture2D( mapRight, uv );',
+				'	} else {',
 
-			'	}',
+				'		gl_FragColor = texture2D( mapRight, uv );',
 
-			'}'
+				'	}',
 
-		].join( '\n' )
+				'}'
 
-	} );
+			].join( '\n' )
 
-	var mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material );
-	_scene.add( mesh );
+		} );
 
-	this.setSize = function ( width, height ) {
+		const mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material );
+		_scene.add( mesh );
 
-		renderer.setSize( width, height );
+		this.setSize = function ( width, height ) {
 
-		var pixelRatio = renderer.getPixelRatio();
+			renderer.setSize( width, height );
 
-		_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
-		_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
+			const pixelRatio = renderer.getPixelRatio();
 
-	};
+			_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
+			_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
 
-	this.render = function ( scene, camera ) {
+		};
 
-		scene.updateMatrixWorld();
+		this.render = function ( scene, camera ) {
 
-		if ( camera.parent === null ) camera.updateMatrixWorld();
+			scene.updateMatrixWorld();
 
-		_stereo.update( camera );
+			if ( camera.parent === null ) camera.updateMatrixWorld();
 
-		renderer.setRenderTarget( _renderTargetL );
-		renderer.clear();
-		renderer.render( scene, _stereo.cameraL );
+			_stereo.update( camera );
 
-		renderer.setRenderTarget( _renderTargetR );
-		renderer.clear();
-		renderer.render( scene, _stereo.cameraR );
+			renderer.setRenderTarget( _renderTargetL );
+			renderer.clear();
+			renderer.render( scene, _stereo.cameraL );
 
-		renderer.setRenderTarget( null );
-		renderer.render( _scene, _camera );
+			renderer.setRenderTarget( _renderTargetR );
+			renderer.clear();
+			renderer.render( scene, _stereo.cameraR );
 
-	};
+			renderer.setRenderTarget( null );
+			renderer.render( _scene, _camera );
 
-};
+		};
+
+	}
+
+}
 
 export { ParallaxBarrierEffect };

+ 89 - 86
examples/jsm/effects/PeppersGhostEffect.js

@@ -8,143 +8,146 @@ import {
  * peppers ghost effect based on http://www.instructables.com/id/Reflective-Prism/?ALLSTEPS
  */
 
-var PeppersGhostEffect = function ( renderer ) {
+class PeppersGhostEffect {
 
-	var scope = this;
+	constructor( renderer ) {
 
-	scope.cameraDistance = 15;
-	scope.reflectFromAbove = false;
+		const scope = this;
 
-	// Internals
-	var _halfWidth, _width, _height;
+		scope.cameraDistance = 15;
+		scope.reflectFromAbove = false;
 
-	var _cameraF = new PerspectiveCamera(); //front
-	var _cameraB = new PerspectiveCamera(); //back
-	var _cameraL = new PerspectiveCamera(); //left
-	var _cameraR = new PerspectiveCamera(); //right
+		// Internals
+		let _halfWidth, _width, _height;
 
-	var _position = new Vector3();
-	var _quaternion = new Quaternion();
-	var _scale = new Vector3();
+		const _cameraF = new PerspectiveCamera(); //front
+		const _cameraB = new PerspectiveCamera(); //back
+		const _cameraL = new PerspectiveCamera(); //left
+		const _cameraR = new PerspectiveCamera(); //right
 
-	// Initialization
-	renderer.autoClear = false;
+		const _position = new Vector3();
+		const _quaternion = new Quaternion();
+		const _scale = new Vector3();
 
-	this.setSize = function ( width, height ) {
+		// Initialization
+		renderer.autoClear = false;
 
-		_halfWidth = width / 2;
-		if ( width < height ) {
+		this.setSize = function ( width, height ) {
 
-			_width = width / 3;
-			_height = width / 3;
+			_halfWidth = width / 2;
+			if ( width < height ) {
 
-		} else {
+				_width = width / 3;
+				_height = width / 3;
 
-			_width = height / 3;
-			_height = height / 3;
+			} else {
 
-		}
+				_width = height / 3;
+				_height = height / 3;
 
-		renderer.setSize( width, height );
+			}
 
-	};
+			renderer.setSize( width, height );
 
-	this.render = function ( scene, camera ) {
+		};
 
-		scene.updateMatrixWorld();
+		this.render = function ( scene, camera ) {
 
-		if ( camera.parent === null ) camera.updateMatrixWorld();
+			scene.updateMatrixWorld();
 
-		camera.matrixWorld.decompose( _position, _quaternion, _scale );
+			if ( camera.parent === null ) camera.updateMatrixWorld();
 
-		// front
-		_cameraF.position.copy( _position );
-		_cameraF.quaternion.copy( _quaternion );
-		_cameraF.translateZ( scope.cameraDistance );
-		_cameraF.lookAt( scene.position );
+			camera.matrixWorld.decompose( _position, _quaternion, _scale );
 
-		// back
-		_cameraB.position.copy( _position );
-		_cameraB.quaternion.copy( _quaternion );
-		_cameraB.translateZ( - ( scope.cameraDistance ) );
-		_cameraB.lookAt( scene.position );
-		_cameraB.rotation.z += 180 * ( Math.PI / 180 );
+			// front
+			_cameraF.position.copy( _position );
+			_cameraF.quaternion.copy( _quaternion );
+			_cameraF.translateZ( scope.cameraDistance );
+			_cameraF.lookAt( scene.position );
 
-		// left
-		_cameraL.position.copy( _position );
-		_cameraL.quaternion.copy( _quaternion );
-		_cameraL.translateX( - ( scope.cameraDistance ) );
-		_cameraL.lookAt( scene.position );
-		_cameraL.rotation.x += 90 * ( Math.PI / 180 );
+			// back
+			_cameraB.position.copy( _position );
+			_cameraB.quaternion.copy( _quaternion );
+			_cameraB.translateZ( - ( scope.cameraDistance ) );
+			_cameraB.lookAt( scene.position );
+			_cameraB.rotation.z += 180 * ( Math.PI / 180 );
 
-		// right
-		_cameraR.position.copy( _position );
-		_cameraR.quaternion.copy( _quaternion );
-		_cameraR.translateX( scope.cameraDistance );
-		_cameraR.lookAt( scene.position );
-		_cameraR.rotation.x += 90 * ( Math.PI / 180 );
+			// left
+			_cameraL.position.copy( _position );
+			_cameraL.quaternion.copy( _quaternion );
+			_cameraL.translateX( - ( scope.cameraDistance ) );
+			_cameraL.lookAt( scene.position );
+			_cameraL.rotation.x += 90 * ( Math.PI / 180 );
 
+			// right
+			_cameraR.position.copy( _position );
+			_cameraR.quaternion.copy( _quaternion );
+			_cameraR.translateX( scope.cameraDistance );
+			_cameraR.lookAt( scene.position );
+			_cameraR.rotation.x += 90 * ( Math.PI / 180 );
 
-		renderer.clear();
-		renderer.setScissorTest( true );
 
-		renderer.setScissor( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height );
-		renderer.setViewport( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height );
+			renderer.clear();
+			renderer.setScissorTest( true );
 
-		if ( scope.reflectFromAbove ) {
+			renderer.setScissor( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height );
+			renderer.setViewport( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height );
 
-			renderer.render( scene, _cameraB );
+			if ( scope.reflectFromAbove ) {
 
-		} else {
+				renderer.render( scene, _cameraB );
 
-			renderer.render( scene, _cameraF );
+			} else {
 
-		}
+				renderer.render( scene, _cameraF );
 
-		renderer.setScissor( _halfWidth - ( _width / 2 ), 0, _width, _height );
-		renderer.setViewport( _halfWidth - ( _width / 2 ), 0, _width, _height );
+			}
 
-		if ( scope.reflectFromAbove ) {
+			renderer.setScissor( _halfWidth - ( _width / 2 ), 0, _width, _height );
+			renderer.setViewport( _halfWidth - ( _width / 2 ), 0, _width, _height );
 
-			renderer.render( scene, _cameraF );
+			if ( scope.reflectFromAbove ) {
 
-		} else {
+				renderer.render( scene, _cameraF );
 
-			renderer.render( scene, _cameraB );
+			} else {
 
-		}
+				renderer.render( scene, _cameraB );
 
-		renderer.setScissor( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height );
-		renderer.setViewport( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height );
+			}
 
-		if ( scope.reflectFromAbove ) {
+			renderer.setScissor( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height );
+			renderer.setViewport( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height );
 
-			renderer.render( scene, _cameraR );
+			if ( scope.reflectFromAbove ) {
 
-		} else {
+				renderer.render( scene, _cameraR );
 
-			renderer.render( scene, _cameraL );
+			} else {
 
-		}
+				renderer.render( scene, _cameraL );
 
-		renderer.setScissor( _halfWidth + ( _width / 2 ), _height, _width, _height );
-		renderer.setViewport( _halfWidth + ( _width / 2 ), _height, _width, _height );
+			}
 
-		if ( scope.reflectFromAbove ) {
+			renderer.setScissor( _halfWidth + ( _width / 2 ), _height, _width, _height );
+			renderer.setViewport( _halfWidth + ( _width / 2 ), _height, _width, _height );
 
-			renderer.render( scene, _cameraL );
+			if ( scope.reflectFromAbove ) {
 
-		} else {
+				renderer.render( scene, _cameraL );
 
-			renderer.render( scene, _cameraR );
+			} else {
 
-		}
+				renderer.render( scene, _cameraR );
 
-		renderer.setScissorTest( false );
+			}
 
-	};
+			renderer.setScissorTest( false );
 
+		};
 
-};
+	}
+
+}
 
 export { PeppersGhostEffect };

+ 30 - 26
examples/jsm/effects/StereoEffect.js

@@ -3,49 +3,53 @@ import {
 	Vector2
 } from '../../../build/three.module.js';
 
-var StereoEffect = function ( renderer ) {
+class StereoEffect {
 
-	var _stereo = new StereoCamera();
-	_stereo.aspect = 0.5;
-	var size = new Vector2();
+	constructor( renderer ) {
 
-	this.setEyeSeparation = function ( eyeSep ) {
+		const _stereo = new StereoCamera();
+		_stereo.aspect = 0.5;
+		const size = new Vector2();
 
-		_stereo.eyeSep = eyeSep;
+		this.setEyeSeparation = function ( eyeSep ) {
 
-	};
+			_stereo.eyeSep = eyeSep;
 
-	this.setSize = function ( width, height ) {
+		};
 
-		renderer.setSize( width, height );
+		this.setSize = function ( width, height ) {
 
-	};
+			renderer.setSize( width, height );
 
-	this.render = function ( scene, camera ) {
+		};
 
-		scene.updateMatrixWorld();
+		this.render = function ( scene, camera ) {
 
-		if ( camera.parent === null ) camera.updateMatrixWorld();
+			scene.updateMatrixWorld();
 
-		_stereo.update( camera );
+			if ( camera.parent === null ) camera.updateMatrixWorld();
 
-		renderer.getSize( size );
+			_stereo.update( camera );
 
-		if ( renderer.autoClear ) renderer.clear();
-		renderer.setScissorTest( true );
+			renderer.getSize( size );
 
-		renderer.setScissor( 0, 0, size.width / 2, size.height );
-		renderer.setViewport( 0, 0, size.width / 2, size.height );
-		renderer.render( scene, _stereo.cameraL );
+			if ( renderer.autoClear ) renderer.clear();
+			renderer.setScissorTest( true );
 
-		renderer.setScissor( size.width / 2, 0, size.width / 2, size.height );
-		renderer.setViewport( size.width / 2, 0, size.width / 2, size.height );
-		renderer.render( scene, _stereo.cameraR );
+			renderer.setScissor( 0, 0, size.width / 2, size.height );
+			renderer.setViewport( 0, 0, size.width / 2, size.height );
+			renderer.render( scene, _stereo.cameraL );
 
-		renderer.setScissorTest( false );
+			renderer.setScissor( size.width / 2, 0, size.width / 2, size.height );
+			renderer.setViewport( size.width / 2, 0, size.width / 2, size.height );
+			renderer.render( scene, _stereo.cameraR );
 
-	};
+			renderer.setScissorTest( false );
 
-};
+		};
+
+	}
+
+}
 
 export { StereoEffect };